Why does my SearchController only match the first character? - ios

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.

Related

Search name or number in `UISearchbar`

In tableViewCell I have userNameLbl with name, userClgLbl with number. I want to search and show data in tableView either name search or number search.
If user search name - based on name I can show data in tableView.
If user search number - based on number I can show data in tableView.
But how to work with both name and number for single search bar. Actually here my data is dynamic from server and number is not phone number.
UISearchBarDelegate added to my class
let searchBar = UISearchBar()
var filteredData: [Any]!
#IBOutlet weak var listTblView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
cell.userNameLbl.text = filteredData[indexPath.row] as? String
cell.userClgLbl.text = clg_uniq[indexPath.row] as? String
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let strArr:[String] = clg_uniq as! [String]
filteredData = searchText.isEmpty ? clg_uniq : strArr.filter({(dataString: String) -> Bool in
// If dataItem matches the searchText, return true to include it
return dataString.range(of: searchText, options: .caseInsensitive) != nil
})
DispatchQueue.main.async {
self.listTblView.reloadData()
}
if searchText == "" {
DispatchQueue.main.async {
searchBar.resignFirstResponder()
}
}
}
//Added these lines after json parsing
self.filteredData = self.clg_uniq
self.listTblView.reloadData()
My example JSON data is
{"log" = (
{
Name = "Name1";
"clg_uniq" = 5c640e7b86e35;
},
{
Name = "Name2";
"clg_uniq" = <null>;
},
{
Name = <null>;
"clg_uniq" = 5c647af5d5c4d;
},
{
Name = "Name4";
"clg_uniq" = 5c647a0427253;
},
{
Name = <null>;
"clg_uniq" = <null>;
},
{
Name = "Name6";
"clg_uniq" = $cuniq";
},
)
}
Add following variables -
var logArray = [Dictionary<String, Any>]() // For all result
var searchedLogArray = [Dictionary<String, Any>]() // For filtered result
var searchActive = false // whenever user search anything
Replace UISearchBarDelegate -
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchActive = searchText.count > 0 ? true : false
let namePredicate = NSPredicate(format: "Name CONTAINS[c] %#", searchText)
let clgUniqPredicate = NSPredicate(format: "clg_uniq CONTAINS[c] %#", searchText)
let compoundPredicate = NSCompoundPredicate.init(orPredicateWithSubpredicates: [namePredicate, clgUniqPredicate])
searchedLogArray = logArray.filter({
return compoundPredicate.evaluate(with: $0)
})
listTblView.reloadData()
}
Replace UITableViewDataSource -
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchActive ? searchedLogArray.count : logArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
let logDict = searchActive ? searchedLogArray[indexPath.row] : logArray[indexPath.row]
// Name
if let name = log["Name"] as? String{
cell.userNameLbl.text = name
}else{
cell.userNameLbl.text = ""
}
// clg_uniq
if let clgUniq = log["clg_uniq"] as? String {
cell.userClgLbl.text = clgUniq
}else{
cell.userClgLbl.text = ""
}
return cell
}
I hope you are persing response as Dictionary<String, Any>
Let me know if you are still having any issue.

Firebase and UISearchbarController searching via the server and not the client -Swift iOS

Does anyone have any info on how to incorporate Firebase into a UISearchController delegate? I can't find any solid info on it. There may possibly be thousands of employees.
I know how to use the search controller delegates updateSearchResultsForSearchController and using a NSPredicate to filter what I'm looking for if I was using NSUserDefaults but using Firebase I'm uncertain.
I've added some more code to my question
I have a custom data model object saved in FirebaseDatabase and I'd like to search on all of the following properties within the object.
lastName
idNumber
deptNumber
position
Searching any of these properties should first show a partial string inside the table cells until the entire string i'm looking for is shown. So if I typed in the letter "S" then all employee last names beginning with "S" should show. If I enter "Sa" the in would filter to those letters". From my understanding I should use "\u{f8ff}" to get the partial search string but no data is returned.
Anyhow here's all the code
My object is:
class Employee{
var firstName: String?
var lastName: String?
var idNumber: String?
var deptNumber: String?
var position: String?
}
My paths
-root
-users
-uid
|_"email":"emailAddress"
|_"userID":"uid"
|_"firstName":"firstName"
|_"lastName":"lastName"
-employees
-hireDate
-uid //this is the same uid from the users node so I know who's who
|_"firstName":"firstName"
|_"lastName":"lastName"
|_"idNum":"idNumber"
|_"deptNumber":"deptNumber"
|_"position":"position"
My rules:
What's happening here is the day an employee is hired they are asked to create a company account using their email address and pw.
At the same time a "employees" path is created with a child being a "hireDate" path and finally the employees "uid" path. This employee "uid" is the path I want to search on from the "employees" node
{
"rules": {
"users" : {
"$uid" : {
".read": true,
".write": "auth != null && auth.uid == $uid"
}
},
"employees": {
"$hireDate": {
"$uid": {
".read": true,
".indexOn": ["lastName", "idNumber", "deptNumber", "position"]
}
}
}
}
}
My searchController
import UIKit
class SearchController: UIViewController{
#IBOutlet var tableView: UITableView!
var searchController: UISearchController!
var employeesToFilter = [Employee]()
var filteredSearchResults = [Employee]()
override func viewDidLoad() {
super.viewDidLoad()
self.searchController = UISearchController(searchResultsController: nil)
self.tableView.delegate = self
//all searchController properties get set here no need to include them though
let ref = FIRDatabase.database().reference()
let employeeRef = ref.child("employees")
employeeRef?.queryOrderedByChild("lastName").queryStartingAtValue("\u{f8ff}").queryLimitedToFirst(20).observeEventType(.ChildAdded, withBlock: {
(snapshot) in
if let dict = snapshot.value as? [String:AnyObject]{
let firstName = dict["firstName"] as! String
let lastName = dict["lastName"] as! String
let idNumber = dict["idNumber"] as! String
let deptNumber = dict["deptNumber"] as! String
let position = dict["position"] as! String
let employee = Employee()
employee.firstName = firstName
employee.lastName = lastName
employee.idNumber = idNumber
employee.deptNumber = deptNumber
employee.position = position
self.employeesToFilter.append(employee)
}
})
self.tableView.reloadData()
}
override func viewDidAppear(animated: Bool) {
self.searchController.active = true
}
deinit {
self.searchController = nil
}
}
//MARK:- TableView Datasource
extension SearchBuildingController: UITableViewDataSource, UITableViewDelegate{
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.filteredSearchResults.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("SearchCell", forIndexPath: indexPath) as! SearchCell
let searchString = self.filteredSearchResults[indexPath.row]
cell.firstNameLabel.text = searchString.firstName
cell.lastNameLabel.text = searchString.lastName
cell.idNumberLabel.text = searchString.idNumber
cell.deptNumberLabel.text = searchString.deptNumber
cell.positionLabel.text = searchString.position
return cell
}
}
//MARK:- SearchController Delegates
extension SearchController: UISearchResultsUpdating, UISearchBarDelegate, UISearchControllerDelegate{
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
tableView.reloadData()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.employeesToFilter.removeAll(keepCapacity: false)
self.filteredSearchResults.removeAll(keepCapacity: false)
let searchText = self.searchController.searchBar.text
let searchPredicate = NSPredicate(format: SELF.lastName CONTAINS [c] %# OR SELF.idNumber CONTAINS [c] %# OR SELF.deptNumber CONTAINS[c] %# OR SELF.position CONTAINS [c] %#", searchText!, searchText!, searchText!, searchText!)
let array = (self.employeesToFilter as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.filteredSearchResults = array as! [Employee]
tableView.reloadData()
}
}
Here is an example of how I have accomplished this using Firebase building a list of campuses. This method loads all of the data that is in the table view up front making it easy to search and filter.
My campus object is pretty simple with an id and a name.
struct Campus {
var id: String
var name: String
}
In the view controller I have two arrays. One is to hold the list of all campuses returned and the other array is for the filtered campuses.
let campuses = [Campus]()
let filteredCampuses = [Campus]()
I then called a method that I had set up to load the campuses from Firebase.
override func viewDidLoad() {
...
getAllCampusesFromFirebase() { (campuses) in
self.campuses = campuses
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
}
Then when performing the search I filter out the campuses comparing the campus name to the search text from the search bar.
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let searchText = searchController.searchBar.text else {
return
}
filteredCampuses = campuses.filter { campus in
return campus.name.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
If you are not loading all of the data up front then Firebase provides some handy methods to call that you can use to filter the data based on the reference path. https://firebase.google.com/docs/database/ios/lists-of-data
queryStarting(atValue) or queryStarting(atValue:childKey:) would probably be the one that you'd want to use in this case.
ref.queryStarting(atValue: Any?)
ref.queryStarting(atValue: Any?, childKey: String?)

Cannot convert value of type '[Record]' to type 'Record' in coercion

Im re-writing an iOS app I made using C# and Xamarin to Swift for obvious reasons of Xamarin's pricing, and low documentation. Following this tutorial for including a UISearchBar on my UITableView, i ran into this error: Cannot convert value of type '[Record]' to type 'Record' in coercion. Record.swift is a struct file I created to store the data that I will retrieve using cloudkit, but Im not sure how this error comes to be.
Here is the problem part of MasterViewController.swift:
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filteredRecords.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS [c] %#", searchController.searchBar.text!)
// Here is where the error shows
let array = (self.records as Record).filteredArrayUsingPredicate(searchPredicate)
self.filteredRecords = array as! [Record]
self.tableView.reloadData()
}
Heres the full MasterViewController.swift:
import UIKit
class MasterViewController: UITableViewController, UISearchResultsUpdating {
var detailViewController: DetailViewController? = nil
var resultSearchController = UISearchController()
var records = [Record]()
var filteredRecords = [Record]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem()
let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
// #REMOVE - Testing implementation until iCloud is setup
self.records = [
Record(album: "The Awakening", artist: "P.O.D.", genre: "Hardcore Rock", year: 2015, speed: "33 1/3", size: 12),
Record(album: "Attack of The Killer B-Sides", artist: "A Day to Remember", genre: "Post-Hardcore", year: 2010, speed: "33 1/3", size: 7),
Record(album: "All I Want", artist: "A Day to Remember", genre: "Post-Hardcore", year: 2011, speed: "33 1/3", size: 7),
Record(album: "The White Stripes", artist: "The White Stripes", genre: "Rock", year: 2003, speed: "45", size: 7),
Record(album: "Save Rock and Roll", artist: "Fall Out Boy", genre: "Punk Rock", year: 2013, speed: "33 1/3", size: 10),
Record(album: "A Perfect Sky", artist: "Yellowcard", genre: "Pop-Punk", year: 2015, speed: "33 1/3", size: 10),
Record(album: "Noise vs Beauty", artist: "Bassnectar", genre: "Dubstep", year: 2014, speed: "33 1/3", size: 12)
]
// Configure the search bar controller
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = self.resultSearchController.searchBar
// Reload the data upon startup
self.tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.collapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// func insertNewObject(sender: AnyObject) {
// objects.insert(NSDate(), atIndex: 0)
// let indexPath = NSIndexPath(forRow: 0, inSection: 0)
// self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
// }
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = records[indexPath.row]
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.resultSearchController.active) {
return self.filteredRecords.count
} else {
return self.records.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
if (self.resultSearchController.active) {
let object = filteredRecords[indexPath.row]
cell.textLabel!.text = object.album
cell.detailTextLabel!.text = object.artist
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
} else {
let object = records[indexPath.row]
cell.textLabel!.text = object.album
cell.detailTextLabel!.text = object.artist
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
}
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
records.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filteredRecords.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS [c] %#", searchController.searchBar.text!)
let array = (self.records as Record).filteredArrayUsingPredicate(searchPredicate)
self.filteredRecords = array as! [Record]
self.tableView.reloadData()
}
}
And Record.swift:
import Foundation
struct Record {
let album : String
let artist : String
let genre : String
let year : Int
let speed : String
let size : Int
}
Hopefully it will be a simple fix. if not, are there any tutorial suggestions? or other places I should ask this instead?
filteredArrayUsingPredicate is an NSArray method, so you should cast it to NSArray:
let array = (self.records as NSArray).filteredArrayUsingPredicate(searchPredicate)
But this still generate an error "[Record] is not convertible to NSArray". This is because an NSArray can only contain Objective-C objects, and Record is a Swift struct and is not convertible to an object. There are two solutions.
Solution 1:
Declare Record as an object
class Record: NSObject {
let album : String
let artist : String
let genre : String
let year : Int
let speed : String
let size : Int
init(album : String, artist : String, genre : String, year : Int, speed : String, size : Int) {
self.album = album
self.artist = artist
self.genre = genre
self.year = year
self.speed = speed
self.size = size
}
}
Then the following code should works
let searchPredicate = NSPredicate(format: "SELF.artist CONTAINS [c] %#", searchController.searchBar.text!)
let array = (self.records as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.filteredRecords = array as! [Record]
Note your original predicate doesn't make sense, and would cause runtime error, because Record is not a String, so I replace it.
Solution 2:
Use the Swift way to filter:
let searchText = searchController.searchBar.text!.lowercaseString
filteredRecords = self.records.filter { (aRecord) -> Bool in
return aRecord.artist.lowercaseString.containsString(searchText)
}
Because I think type of your self.records is [Record] and you are trying to casting it as single Record which is not an Array. So it is not possible.
Try this:
let array = (self.records as [Record]).filteredArrayUsingPredicate(searchPredicate)
UPDATE:
Update your updateSearchResultsForSearchController method like this:
func updateSearchResultsForSearchController(searchController: UISearchController)
{
self.filteredRecords.removeAll(keepCapacity: false)
self.filteredRecords = self.records.filter() {
($0.album.lowercaseString).containsString(searchController.searchBar.text!.lowercaseString)
}
self.tableView.reloadData()
}
If you want to search for both album and artist then replace this line:
($0.album.lowercaseString).containsString(searchController.searchBar.text!.lowercaseString)
with this line:
($0.album.lowercaseString).containsString(searchController.searchBar.text!.lowercaseString) || ($0.artist.lowercaseString).containsString(searchController.searchBar.text!.lowercaseString)

Swift Json count and create TableView

I need to create a tableview and fill that with database information that I take with json. This is the response I get from the database with json
{
"news": [
{
"id": "35",
"type": "news",
"title": "final test for offer",
"city": "Mumbai",
"description": "Test description",
"image": "http://www.saimobileapp.com/mobileappbackend/news/IMG_0421.JPG"
},
{
"id": "31",
"type": "news",
"title": "new test",
"city": "Mumbai",
"description": "yes its a test msg",
"image": "http://www.saimobileapp.com/mobileappbackend/news/Chrysanthemum.jpg"
},
{
"id": "30",
"type": "news",
"title": "This is a test news",
"city": "Mumbai",
"description": "Test description",
"image": "http://www.saimobileapp.com/mobileappbackend/news/1.jpg"
}
]
}
These are 3 different news with title etc., so I need to count it as I will add new, and create a table view in base of that.
This is my code now to get the database information with new EDIT:
func LoadNews() {
let post:NSString = ""
NSLog("PostData: %#",post);
let url:NSURL = NSURL(string: "http://saimobileapp.com/services/sai_news.php")!
let postData:NSData = post.dataUsingEncoding(NSASCIIStringEncoding)!
let postLength:NSString = String( postData.length )
let request:NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = postData
request.setValue(postLength as String, forHTTPHeaderField: "Content-Length")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
var reponseError: NSError?
var response: NSURLResponse?
var urlData: NSData? = NSURLConnection.sendSynchronousRequest(request, returningResponse:&response, error:&reponseError)
if ( urlData != nil ) {
let res = response as! NSHTTPURLResponse!;
NSLog("Response code: %ld", res.statusCode);
if (res.statusCode >= 200 && res.statusCode < 300)
{
let responseData:NSString = NSString(data:urlData!, encoding:NSUTF8StringEncoding)!
NSLog("Response ==> %#", responseData);
var error: NSError?
var Title: [String] = []
if let jsonData = NSJSONSerialization.JSONObjectWithData(urlData!, options: nil, error: &error) as? [String:AnyObject] { // dictionary
if let locationsArray = jsonData["news"] as? [[String:AnyObject]] { // array of dictionaries
for locationDictionary in locationsArray { // we loop in the array of dictionaries
if let location = locationDictionary["title"] as? String { // finally, access the dictionary like you were trying to do
Title.append(location)
var SaveTitle = save.setObject(Title, forKey: "NewsTitle")
}
}
}
}
}
}
}
And for TableView i use that now :
// MARK: UITextFieldDelegate Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var FormName = save.arrayForKey("NewsTitle")!
return FormName.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var FormName = save.arrayForKey("NewsTitle")!
var cell:UITableViewCell = self.TableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
let row = indexPath.row
cell.textLabel?.text = FormName[indexPath.row] as! String
if (indexPath.row % 2 == 0) {
cell.backgroundColor = UIColor.clearColor()
}else{
cell.backgroundColor = UIColor.clearColor()
cell.textLabel?.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.0)
}
cell.textLabel?.textColor = UIColor.whiteColor()
return cell
}
// MARK: UITableViewDelegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
TableView.deselectRowAtIndexPath(indexPath, animated: false)
let row = indexPath.row
How can i show the description in the second page when i tap on the cell?
Can anyone please help me? Thanks in advance.
Follow these steps to render your table view:
Assuming you have set your view controller as datasource and delegate for UITableViewController.
In your table view controller' subclass: inside loadView or viewWillAppear make server call to fetch the details. [This you might already be doing]
Create a global parameter to hold to that data. e.g. self.vehicles = jsonData["news"]
After server response, call reloadDatamethod on self.tableView. This will trigger calls to your table data sources methods - numberOfRowsInSection:, cellForRowAtIndexPath: etc.
Return correct values from data source methods. Based on your needs you can create a custom cell and use self.vehicles to fetch & render data on it.
Edit:
Example
Your data is a array of dictionary where array count will drive the number of cells in the table. That said, lets say there are 5 dictionaries in your array so you have 5 cells. When you get a call on cellForRowAtIndexPath:, use 'index path.row' to get the right dictionary mapped to the cell in request. Now, fetch the values inside that dictionary and set them on cell. Like this:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
// Configure the cell...
let cellId: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId) as UITableViewCell
if let ip = indexPath {
var data = self.vehicles[ip.row] as NSDictionary
cell.textLabel.text = data.valueForKey("title") as String
}
return cell
}
Similarly implement didSelectRowAtIndexPath and then fetch & pass on description to your target view controller for display.
Edit 2 (On OP request):
Based on second screen design (table controller or simple view controller), create a new controller class. Then, as I mentioned above, implement didSelectRowAtIndexPath something like below:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var description = String()
if let ip = indexPath {
var data = self.vehicles[ip.row] as NSDictionary
description = data.valueForKey("description") as String
}
var descriptionVC = SecondScreenVC(withDescription: description)
self.navigationController.pushViewController(descriptionVC, animated: true)
}
Some references:
Apple Docs on UITableViewController
Handling UITableViewController Programmatically
The object of key news is an Array (of dictionaries), not a Dictionary
if let vehicles = jsonData["news"] as? NSArray {
for vehicle in vehicles {
let vehiclesKeys = vehicle.allKeys
println(vehiclesKeys)
println("123")
}
}
}
vehicles.count gives you the number of items in the array.

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