UITableView sectioning - ios

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.

Related

I want to know why the results are repeated in the table view

I get the value through the php file of the web server.
I hope to bring the results from DB when I search.
There are two problems with my code now.
Spelling that is not included in the values contained in the DB also produces results.
The results come out repeatedly.
First of all, this is the json result I'm getting.
[
{
"name" : "name1",
"id" : "1"
},
{
"name" : "name2",
"id" : "2"
},
{
"name" : "name3",
"id" : "3"
},
{
"name" : "name4",
"id" : "4"
},
{
"name" : "name5",
"id" : "5"
},
{
"name" : "name6",
"id" : "6"
},
{
"name" : "name7",
"id" : "7"
},
{
"name" : "name8",
"id" : "8"
},
{
"name" : "name9",
"id" : "9"
},
{
"name" : "name10",
"id" : "10"
},
{
"name" : "name11",
"id" : "11"
},
{
"name" : "name12",
"id" : "12"
}
]
But in emulators, the results come out this way.
name1
name1
name2
name1
name2
name3
.
.
.
I wonder why the results come out like this.
This is my code.
import UIKit
import Alamofire
import SwiftyJSON
class MyTableViewController: UITableViewController, UISearchBarDelegate {
var arrID = [String]()
var arrName = [String]()
var filteredData = [String]()
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet var mytableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
self.mytableview.delegate = self
self.mytableview.dataSource = self
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return filteredData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
cell.textLabel?.text = filteredData[indexPath.row]
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let url = "http://url/name.php?name=" + searchText
print(url)
AF.request(url, method: .get).responseJSON { (myresponse) in
switch myresponse.result {
case .success:
let myresult = try? JSON(data: myresponse.data!)
print(myresult!)
let resultArray = myresult!
self.arrID.removeAll()
self.arrName.removeAll()
for i in resultArray.arrayValue {
let id = i["id"].stringValue
self.arrID.append(id)
let name = i["name"].stringValue
self.arrName.append(name)
self.filteredData.append(contentsOf: self.arrName)
}
if searchText == "" {
self.filteredData = []
} else {
for data in self.filteredData {
if data.lowercased().contains(searchText.lowercased()) {
self.filteredData.append(data)
}
}
}
self.mytableview.reloadData()
case .failure:
print(myresponse.error!)
}
}
}
}
You probably want to change this:
for i in resultArray.arrayValue {
let id = i["id"].stringValue
self.arrID.append(id)
let name = i["name"].stringValue
self.arrName.append(name)
self.filteredData.append(contentsOf: self.arrName)
}
To this:
for i in resultArray.arrayValue {
let id = i["id"].stringValue
self.arrID.append(id)
let name = i["name"].stringValue
self.arrName.append(name)
}
self.filteredData.append(contentsOf: self.arrName)
I'm confused why you are doing this, though:
for data in self.filteredData {
if data.lowercased().contains(searchText.lowercased()) {
self.filteredData.append(data)
}
}
Because for each entry in your JSON you add all the data you read so far to your filtered array again. And then you finally add all the entries that contain your search text to filteredArray again.
The first issue will be fixed by removing the line
self.filteredData.append(contentsOf: self.arrName)
from the first loop.
The second loop that does the filtering then needs to be changed to iterate over ˋarrNameˋ instead and you need to clear ˋfilteredDataˋ before.
An even better option would be to use the filter method:
filteredData = arrName.filter { data in
data.lowercased().contains(searchText.lowercased())
}

Checking if cells are selected or not when button is pressed swift

In this photo as you can see user can turn on or off the switch for each cell. At the end when the user presses the button I need to add the selected cell (the cells that have a switch on) to an array to send it to API.
When the user clicks the button (at the bottom of the screen) I should get the unitNo and personId and store them in my struct
My model:
struct Itemm : Codable {
var unitNo:Int?
var personId:Int?
}
struct welcome {
var items : [Itemm?]
}
and I have to send an array like below:
{
"items": [
{
"unitNo": 0,
"personId": 0
}
]
}
I'm some confused as to how I should access the cell data in UIButton with if statement and then storing them. I would be grateful for any help.
Tableview:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell") as? SmsTableViewCell
cell?.PhonNumberLbl.text = data![indexPath.section].contacts[indexPath.row]?.phoneNumber
cell?.NameLbl.text = data![indexPath.section].contacts[indexPath.row]?.name
cell?.selectedTF.isOn = (data![indexPath.section].contacts[indexPath.row]?.selected)!
return cell!
}
API response:
[
{
"contacts": [
{
"id": 9827,
"selected": true,
"phoneNumber": "09203137745",
"name": "owner"
},
{
"id": 10159,
"selected": true,
"phoneNumber": "08523698522",
"name": "hff"
},
{
"id": 10161,
"selected": true,
"phoneNumber": "09586731218",
"name": "hjdldue"
}
],
"unitNo": 1,
"unitPlaque": "jack",
"billText": "texttext"
},
{
"contacts": [
{
"id": 10145,
"selected": true,
"phoneNumber": "09123809556",
"name": "mmm"
},
{
"id": 10160,
"selected": true,
"phoneNumber": "85233366888",
"name": "ttt"
}
],
"unitNo": 2,
"unitPlaque": "mm",
"billText": "texttext"
}
]
I think I didn't clearly explain what I'm looking for.
The first method I call is a GET method which gives me the data that I'm presenting in the table. The user could only change the switch, the user can change it as many times as they want until he/she presses the button (at the bottom of the screen).
When the button is pressed, I need to get the hidden data behind each cell and store them in my struct and send it to API which has another method. When the user presses the button I need to extract the 2 properties from the cells that has a switch on.
Presenter represents this:
func sendSmsForAllTheMembers(AptId:String , data:[String:Int])
{
ApiGenerator.request(targetApi: ApartemanService.sendSms(aptId: AptId, data: data), responseModel: Nil.self, success: { (response) in
if response.response.statusCode == 200 {
self.view?.SendingSmsSuccess()
}else {
do{
var errorMessage = try response.response.mapString()
errorMessage = errorMessage.replacingOccurrences(of: "\"", with: "",
options: NSString.CompareOptions.literal, range:nil)
self.view?.SendingSmsFailed(errorMessage: errorMessage)
}catch let error{
print(error)
self.view?.SendingSmsFailed(errorMessage: "error")
}
}
}) { (error) in
self.view?.SendingSmsFailed(errorMessage: "error")
}
}
Your first problem is that you need to store the switch state in your view controller somehow; you can't store it in the cell directly since cells are re-used as your table view scrolls.
While you could store the switch state in your model struct, I would probably not do this, as it makes your struct mutable. I would use a Set<IndexPath> to track selections.
Your next problem is knowing when the switch is changed in a cell. You can provide a closure to your UITableviewCell subclass to handle this.
SmsCell
var switchHandler: ((Bool)->Void)?
#IBAction func switchChanged(_ sender: UISwitch) {
self.switchHandler?(sender.isOn)
}
View Controller
var selectedCells = Set<IndexPath>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell") as! SmsTableViewCell
let cellData = self.data![indexPath.section]
let contact = cellData.contacts[indexPath.row]
cell.PhonNumberLbl.text = contact.phoneNumber
cell.NameLbl.text = contact.name
cell.selectedTF.isOn = self.selectedCells.contains(indexPath)
cell.switchHandler = { (switchState) in
if switchState {
self.selectedCells.insert(indexPath)
} else {
self.selectedCells.remove(indexPath)
}
}
return cell
}
Your final task is to create struct that you can encode into the required JSON:
struct Itemm: Codable {
let unitNo: Int
let personId: Int
}
struct Welcome: Codable {
var items:[Itemm]
}
View Controller
#IBAction func sendButtonTapped(_ sender: UIButton) {
var items = [Itemm]()
for indexPath in self.selectedCells {
let data = self.data![indexPath.section]
let contact = data.contacts[indexPath.row]
let newItem = Itemm(unitNo: data.unitNo, personId: contact.id)
items.append(newItem)
}
let welcome = Welcome(items: items)
// Now you can encode and send welcome
}
ok, as far as i got from what are you trying to do,
1- give the switch tag,
inside the
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch.tag = indexPath.row
switch.addTarget(self, action: #selector(onSwitchValueChanged), for: .touchUpInside)
}
And in the didChange Method of the Switch,
func onSwitchValueChanged(_ switch: UISwitch) {
if switch.on {
selectedArray.append(dataArray[switch.tag])
} esle {
selectedArray.remove(at: switch.tag)
}
SmsCell
var switchHandler: ((Bool)->Void)?
#IBAction func switchChanged(_ sender: UISwitch) {
self.switchHandler?(sender.isOn)
}
View Controller
var selectedCells = Set<IndexPath>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell") as! SmsTableViewCell
let cellData = self.data![indexPath.section]
let contact = cellData.contacts[indexPath.row]
cell.PhonNumberLbl.text = contact.phoneNumber
cell.NameLbl.text = contact.name
cell.selectedTF.isOn = self.selectedCells.contains(indexPath)
cell.switchHandler = { (switchState) in
if switchState {
self.selectedCells.insert(indexPath)
} else {
self.selectedCells.remove(indexPath)
}
}
return cell
}
struct Itemm: Codable {
let unitNo: Int
let personId: Int
}
struct Welcome: Codable {
var items:[Itemm]
}
View Controller
#IBAction func sendButtonTapped(_ sender: UIButton) {
var items = [[String:Int]]()
for indexPath in self.selectedCells {
let data = self.data![indexPath.section]
let contact = data.contacts[indexPath.row]
items.append(["Number" : data.unitNo, "ID" : contact!.id])
}
let welcome = ["items" : items]
print(welcome)
Presenter.sendSmsForAllTheMembers(AptId: aptId, data: welcome)
}
the final answer is a combination of the answer that some people gave me here and some that I found it myself
You can update your Model objects when switch on/off function and also give switch tag as a cell.indexpath.
Update model objects like this Ex :-
if switch == on {
items[switch_tag].unitNo =0
} else {
items[switch_tag].unitNo =1
}

Parse JSON array with Swiftyjson in Swift 2.3

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.

How can i access value of other Mappble class object.(Alamofire Object Mapper)

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
}
}

Group and Sort Dictionary Data in TableViewController with Swift

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]
}
}

Resources