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]
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!
}
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]
}
}
Related
I'm working with Swiftyjson in Swift 2.3. I've been able to parse json arrays into a UITableview with no problem until I got the following json response:
{
"CAR": [
{
"SID": "1",
"NAME": "BMW",
},
{
"SID": "2",
"NAME": "MERCEDES",
},
],
"BIKE": [
{
"SID": "3",
"NAME": "KAWASAKI",
},
{
"SID": "4",
"NAME": "HONDA",
},
]
}
QUESTION How do I parse "CAR" and "BIKE" into tableview sections and have their items under each section? I've managed to get the "keys" using:
// Other Code
let json = JSON(data)
for (key, subJson) in json {
self.array.append(key)
}
print(self.array)
["CAR", "BIKE"]
I wanted to know how to loop through each section and get their items properly. Any help would be great!
Since you haven't shown where you are getting your json data from, I have tested this by having your JSON above in a .json file. Also this doesn't use SwiftyJSON but you will be able to modify the syntax to get the same result.
class TableViewController: UITableViewController {
var tableData = Dictionary<String,Array<Dictionary<String,String>>>()
var sections = Array<String>()
override func viewDidLoad() {
super.viewDidLoad()
load(file: "document")
}
func load(file:String) {
guard let path = Bundle.main.path(forResource: file, ofType: "json") else { return }
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return }
guard let json = try? JSONSerialization.jsonObject(with: data) else { return }
guard let dict = json as? Dictionary<String,Array<Dictionary<String,String>>> else { return }
self.tableData = dict
self.sections = dict.keys.sorted()
self.tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return self.sections.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let key = self.section[section]
return key
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let key = self.section[indexPath.section]
return self.tableData[key]?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Configure the cell...
let key = self.section[indexPath.section]
let cellData = self.tableData[key]?[indexPath.row]
cell.textLabel?.text = cellData?["NAME"]
cell.detailTextLabel?.text = "SID: \(cellData?["SID"] ?? "Unknown")"
return cell
}
}
This is how it looks on an iPhone.
in my app i am first time using AlamofireObjectMapper.
So i am mapping api response data in one class and then i want to use that data.
So here is my code that how i map object
extension OrderListViewController
{
func get_order_list()
{
let url = "\(OrderURL)get_New_order_byPharmacy"
let param : [String : AnyObject] = [
"pharmacyId" : "131"
]
Alamofire.request(.GET, url, parameters: param, encoding: .URL).responseObject { (response:Response<OrderList, NSError>) in
let OrderList = response.result.value
print(OrderList!.Message)
}
}
}
and here is the class where i saving my data
class OrderList: Mappable {
var Message : String!
var Status : Int!
var result:[OrderResult]?
required init?(_ map: Map){
}
func mapping(map: Map) {
Message <- map["Message"]
Status <- map["Status"]
result <- map["Result"]
}
}
now in my OrderListViewController i want to use this data so how can i use this??
class OrderListViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var table_OrderList: UITableView!
override func viewDidLoad() {
slideMenuController()?.addLeftBarButtonWithImage(UIImage(named: "ic_menu_black_24dp")!)
slideMenuController()?.addRightBarButtonWithImage(UIImage(named: "ic_notifications_black_24dp")!)
get_order_list()
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : OrderList_Cell = tableView.dequeueReusableCellWithIdentifier("OrderList_Cell") as! OrderList_Cell
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
}
for example i want to print message value in my tableview cell label. so how can i get that value form OrderList?
Thanks slava its give me some solution. but my json response give me array. So how can i manage it? and i want to return in numberofrowinSetcion is count of array so how can i do this. please see my updated question.
here is my api response.
{
"Status": 1,
"Message": "records are available",
"Result": [
{
"id": 30162,
"status_id": 2,
"status_type": "New Order",
"created_date": "2016-05-11T10:45:00.6779848",
"created": "11 May 2016"
},
{
"id": 30170,
"status_id": 2,
"status_type": "New Order",
"created_date": "2016-05-12T07:01:00.6968385",
"created": "12 May 2016"
},
{
"id": 30171,
"status_id": 2,
"status_type": "New Order",
"created_date": "2016-05-12T09:12:53.5538349",
"created": "12 May 2016"
},
{
"id": 30172,
"status_id": 2,
"status_type": "New Order",
"created_date": "2016-05-12T09:46:09.4329398",
"created": "12 May 2016"
},
{
"id": 30173,
"status_id": 2,
"status_type": "New Order",
"created_date": "2016-05-12T11:26:58.3211678",
"created": "12 May 2016"
},
{
"id": 30178,
"status_id": 2,
"status_type": "New Order",
"created_date": "2016-05-16T07:34:19.9128517",
"created": "16 May 2016"
}
]
}
You need a local variable in your controller to store all the received information that will be used to fill the table. Something like that should do:
class OrderListViewController: ... {
private var orderList: OrderList? // <- the local variable needed
...
}
extension OrderListViewController {
func get_order_list() {
...
Alamofire
.request(...)
.responseObject { (response:Response<OrderList, NSError>) in
switch response.result {
case .Success(let value):
self.orderList = value // <- fill the local variable with the loaded data
self.tableView.reloadData()
case .Failure(let error):
// handle error
}
}
}
...
}
extension OrderListViewController: UITableViewDataSource {
...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : OrderList_Cell = tableView.dequeueReusableCellWithIdentifier("OrderList_Cell") as! OrderList_Cell
// I assume 'OrderList_Cell' class has outlet for status type named 'statusTypeLabel' and OrderResult.statusType is of type String
if let orderList = orderList, orderResults = orderList.result {
cell.statusTypeLabel.text = orderResults[indexPath.row].statusType // <- use of the locally stored data
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let orderList = orderList, orderResults = orderList.result {
return orderResults.count
} else {
return 0
}
}
}
Note: the code should be correct in case you receive the single object in JSON from backend.
If backend sends the array of objects - you'll need to use array to store local data (private var listOfOrderLists: [OrderList]) and use Alamofire.request(...).responseArray(...) instead. But the idea about local variable is still the same.
typealias FailureHandler = (error: AnyObject) -> Void
typealias SuccessHandler = (result: AnyObject) -> Void
class WebServiceManager: NSObject {
class func getDataFromService(mehodName:String,success:(result:AnyObject)->(), apiError:(FailureHandler))
{
let url = "\(OrderURL)get_New_order_byPharmacy"
let param : [String : AnyObject] = [
"pharmacyId" : "131"
]
alamoFireManager!.request(.GET, url)
.responseJSON { response in
print(response.response!)
print(response.result)
CommonFunctions.sharedInstance.deactivateLoader()
switch response.result {
case .Success(let JSON):
print("Success with JSON: \(JSON)")
guard let _ = JSON as? NSMutableArray else {
apiError(error: "")
return
}
let listOfItem:NSMutableArray = NSMutableArray()
for (_, element) in adsArray.enumerate() {
let adsItem = Mapper<OrderList>().map(element)
listOfItem.addObject(adsItem!)
}
success(result:listOfItem)
case .Failure(let data):
print(data)
}
}
}
class OrderListViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var table_OrderList: UITableView!
var listOFOrder:NSMutableArray =[]
override func viewDidLoad() {
slideMenuController()?.addLeftBarButtonWithImage(UIImage(named: "ic_menu_black_24dp")!)
slideMenuController()?.addRightBarButtonWithImage(UIImage(named: "ic_notifications_black_24dp")!)
WebServiceManager.getDataFromService("", success: { (result) in
listOFOrder = result as NSMutableArray
self.recordTable?.reloadData()
}) { (error) in
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : OrderList_Cell = tableView.dequeueReusableCellWithIdentifier("OrderList_Cell") as! OrderList_Cell
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listOFOrder.count
}
}
I had my tableView populating as it should for a little while but then made some tweaks to how I get my data from Parse and since then I can't get my tableView to display without crashing with fatal error: Array index out of range
Here is my code:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var vaccineEntry: Array<Array<Dictionary<String,String>>> = [[
["name" : "Rabies 1-yr", "detail": "None"],
["name" : "Rabies 3-yr", "detail": "None"],
["name" : "Distemper", "detail": "None"],
["name" : "Parvovirus", "detail": "None"],
["name" : "Adenovirus", "detail": "None"]],
[
["name" : "Parainfluenza", "detail": "None"],
["name" : "Bordetella", "detail": "None"],
["name" : "Lyme Disease", "detail": "None"],
["name" : "Leptospirosis", "detail": "None"],
["name" : "Canine Influenza", "detail": "None"]
]]
var sections = ["Core Vaccines", "Non-Core Vaccines"]
var titles = [["Rabies 1-yr", "Rabies 3-yr", "Distemper", "Parvovirus", "Adenovirus"], ["Parainfluenza", "Bordetella", "Lyme Disease", "Leptospirosis", "Canine Influenza"]]
var textCellIdenifier = "vaccineCell"
// Section Headers
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sections[section]
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.sections.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.titles[section].count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdenifier, forIndexPath: indexPath) as UITableViewCell
let object = vaccineEntry[indexPath.section][indexPath.row]
cell.textLabel?.text = object["name"]
cell.detailTextLabel?.text = object["detail"]
return cell
}
}
This line crashes with the fatal error:
let object = vaccineEntry[indexPath.section][indexPath.row]
Or I can not make an object variable and just go:
cell.textLabel?.text = vaccineEntry[indexPath.section][indexPath.row]["name"]
cell.textLabel?.text = vaccineEntry[indexPath.section][indexPath.row]["detail"]
But same deal, just crashes on the ["name"] line. What am I doing wrong? Any help is much appreciated!
Works perfectly fine for me without any errors or warnings!
PFB the code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var vaccineEntry: Array<Array<Dictionary<String,String>>> = [[
["name" : "Rabies 1-yr", "detail": "None"],
["name" : "Rabies 3-yr", "detail": "None"],
["name" : "Distemper", "detail": "None"],
["name" : "Parvovirus", "detail": "None"],
["name" : "Adenovirus", "detail": "None"]],
[
["name" : "Parainfluenza", "detail": "None"],
["name" : "Bordetella", "detail": "None"],
["name" : "Lyme Disease", "detail": "None"],
["name" : "Leptospirosis", "detail": "None"],
["name" : "Canine Influenza", "detail": "None"]
]]
var sections = ["Core Vaccines", "Non-Core Vaccines"]
var titles = [["Rabies 1-yr", "Rabies 3-yr", "Distemper", "Parvovirus", "Adenovirus"], ["Parainfluenza", "Bordetella", "Lyme Disease", "Leptospirosis", "Canine Influenza"]]
var textCellIdenifier = "vaccineCell"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: self.textCellIdenifier)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sections[section]
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.sections.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.titles[section].count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdenifier, forIndexPath: indexPath) as UITableViewCell
let object = vaccineEntry[indexPath.section][indexPath.row]
cell.textLabel?.text = object["name"]
cell.detailTextLabel?.text = object["detail"]
return cell
}
}
The running code on simulator:
Here's the work-around that made the code simpler and work better!
var sections = ["Core Dog Vaccines", "Non-Core Dog Vaccines"]
var titles = [["Rabies 1-yr", "Rabies 3-yr", "Distemper", "Parvovirus", "Adenovirus"], ["Parainfluenza", "Bordetella", "Lyme Disease", "Leptospirosis", "Canine Influenza"]]
var details = [["No Dogument", "No Dogument", "No Dogument", "No Dogument", "No Dogument"], ["No Dogument", "No Dogument", "No Dogument", "No Dogument", "No Dogument"]]
var textCellIdenifier = "vaccineCell"
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdenifier, forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = self.titles[indexPath.section][indexPath.row]
cell.detailTextLabel?.text = self.details[indexPath.section][indexPath.row]
return cell
}
Instead of having a complicated Array< Array< Dictionary< String,String>>> I just made a separate array for the details.. Ugh making it complicated to begin with sucks but still learning everyday! Now I can load data from parse like so:
let rabies1Query = PFQuery(className: "Vaccine")
rabies1Query.whereKey("userId", equalTo: (PFUser.currentUser()?.objectId)!)
rabies1Query.whereKey("newID", equalTo: self.dogObject)
rabies1Query.whereKey("vaccineType", equalTo: "Rabies 1-yr")
rabies1Query.findObjectsInBackgroundWithBlock({ (object, error) -> Void in
if error == nil {
if let object = object {
for object in object {
if object["expired"] as! Bool == true {
self.details[0][0] = self.expired
print("printing rabies 1-yr - EXPIRED")
} else if object["expired"] as! Bool == false {
self.details[0][0] = self.upToDate
print("printing rabies 1-yr - UP TO DATE")
}
}
}
} else {
print("printing rabies 1-yr - No Document")
}
})
And so on for each title changing the location of the details array location. Hope someone else can see and learn something!
I have Data in Dictionary format like this:
var data: [[String:AnyObject]] =
[
[
"id": "1",
"title": "A Title",
"Detail": "This is a String"
],
[
"id": "2",
"title": "A Title Again",
"Detail": "This is a String"
]
]
and my TableViewController lists all "title" data.
I am implementing a SearchController for user to search for a specific data from the "title" only.
My code seems to display results matching just the first character from the search query.
For example: if user inputs "A", all the title results with "A" are displayed but if user goes ahead with "A " (with a space), everything in the searchResult disappears.
Here's my attempt:
class TVC: UITableViewController, UISearchResultsUpdating {
let Search = data
var filteredSearch = [[String:AnyObject]]()
var resultSearchController = UISearchController()
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.resultSearchController.active)
{
return self.filteredSearch.count
}
else
{
return self.Search.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell?
if (self.resultSearchController.active)
{
cell!.textLabel?.text = (filteredSearch[indexPath.row]["title"] as! String)
return cell!
}
else
{
cell!.textLabel?.text = (Search[indexPath.row]["title"] as! String)
return cell!
}
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
self.filteredSearch.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.Search as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.filteredSearch = array as! [[String:AnyObject]]
self.tableView.reloadData()
}
You got your predicate wrong. The way it is setup now, self in predicate refers to the objects in an array which are dictionaries, and dictionaries don't have substrings :) In order to make it work, we have to tell the predicate to check value under specific key. This can be done in two ways :
let searchPredicate = NSPredicate(format: "title CONTAINS[c] %#", searchController.searchBar.text!)
// OR
let searchPredicate = NSPredicate(format: "SELF[\"title\"] CONTAINS[c] %#", searchController.searchBar.text!)
This way, the predicate will check the value under title key if it contains the text from the search bar.
I do not know why it worked with single letter though.
I am quite new to creating apps, and I am starting with iOS development with Swift. I really hope that someone will take a bit of time to help me out, because I am rather stuck now. So I am creating an app with a master-detail tableview. I will fetch my data externally using Alamofire, and the returned data will be JSON, which I can parse with SwiftyJSON.
I am able to display the data just fine, that is working fine for me. But what I want to achieve, and what I seem to can't get my head around, is how to divide my items in alphabetical sections with titleForHeaderInSection, and able to navigate around with sectionIndexTitlesForTableView.
The data received from my API looks something like this (The format can be changed, since I control the API):
{
"A": [
{
"id": "1",
"char": "A",
"title": "ABC",
"body": "Denne sang hedder ABC"
},
{
"id": "2",
"char": "A",
"title": "Abel spandabel",
"body": "Denne sang hedder Abel spandabel"
},
{
"id": "3",
"char": "A",
"title": "Aha aha aha",
"body": "Denne sang hedder Aha aha aha"
}
],
"B": [
{
"id": "4",
"char": "B",
"title": "Bussemand",
"body": "Denne sang hedder Bussemand"
},
{
"id": "5",
"char": "B",
"title": "Balademager",
"body": "Denne sang hedder Balademager"
}
],
"D": [
{
"id": "6",
"char": "D",
"title": "Dukke mand",
"body": "Denne sang hedder Dukke mand"
},
{
"id": "7",
"char": "D",
"title": "Dansevisen",
"body": "Denne sang hedder Dansevisen"
}
]
}
Then I have a Song.swift file that defines a struct with some properties for my individual items. I looks something like this:
struct Song {
var title : String
var body : String
var char : String
}
Finally, I have a ViewController that implements the necessary logic in order to draw the tableview with the data returned
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var songsTableView: UITableView!
var songs = [Song]()
func addSong(song: Song) {
self.songs.append(song)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return songs.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
var currentSong = songs[indexPath.row]
cell.textLabel?.text = currentSong.title
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var nextScreen = segue.destinationViewController as! SingleViewController
if let indexPath = self.songsTableView.indexPathForSelectedRow() {
let selectedSong = songs[indexPath.row]
nextScreen.currentSong = selectedSong
}
}
func getSongs() {
Alamofire.request(.GET, "http://MYAPI.COM")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
// TODO: Come up with some better names for the "subJson"
for (key: String, subJson: JSON) in json {
// TODO: Come up with some better names for the "subSubJson"
for (key: String, subSubJson: JSON) in subJson {
var title: String = subSubJson["title"].string!
var body: String = subSubJson["body"].string!
var char: String = subSubJson["char"].string!
var song = Song(title: title, body: body, char: char)
self.addSong(song)
}
}
self.songs.sort({$0.title < $1.title})
self.songsTableView!.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.getSongs()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I know that this should probably be text book stuff, but I've been at it for three days now, and I am starting to lose my mind a bit. So I really hope that someone will take their time to help me out here, and address some of the things I might do wrong
Thanks in advance!
For what you want you need to implement 2 delegate methods of tableview:
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int
func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]!
but you'd need to modify your model a bit to make it more suitable for this kind of sectioning.
If your api returns the data already splitted into sections I'd keep the songs grouped in dictionary like so: var songs:[String:[Song]?] = [:]
then in the api handler I'd do something like:
for (letter, songsJsonForLetter) in json.dictionaryValue {
for songJson in songsJsonForLetter.arrayValue {
var newSong = Song(title:songJson["title"].string!, body: songJson["bopy"].string!, char: letter)
if nil != self.songs[letter] {
self.songs[letter]!.append(newSong)
}else{
self.songs[letter] = [newSong]
}
}
}
this way you'll have the songs parsed directly into a dictionary where the key is the letter ["A",[Song1, Song2, Song3], "D":[Song4, Song5]]...
now all you need to do is to implement the 2 delegates for the table view needed for the section indexing, something like:
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
return index
}
func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! {
return self.songs.keys.array.sorted{$0 < $1}
}
and of course modify your code that gets the song for index path in the cellForRowAtIndePath to something like:
...
let sectionLetter = sectionIndexTitlesForTableView(tableView)[indexPath.section] as! String
var currentSong = songs[sectionLetter]!.sorted{$0.title<$1.title}[indexPath.row]
...
PS. don't forget to chage also the numberOfSections and rowsForSection to something like:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return songs.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionLetter = sectionIndexTitlesForTableView(tableView)[section] as! String
return songs[sectionLetter].count
}
hope this helps, gl
In numberOfSectionsInTableView, you'll need to return the number of sections. Maybe that's 26, or maybe you don't want to show blank sections.
In numberOfRowsInSection:, you'll need to return the number of rows for that section. So if section 0 is a, return the number of rows for a.
In cellForRowAtIndexPath:, use the indexPath's row and section properties to return the specific cell.
A good way to start might be to break the data up into arrays, one section per letter.
When you parse the response you will get a dictionary with A, B, ... Z as keys and corresponding details are values. You should implement numberOfSectionsInTableView to return number of keys in this dictionary, numberOfRowsInSection to return number of entires for each Alphabet key and cellForRowAtIndexPath to display proper values.