Hi I have two arrays and only one array is updating with search bar.. I keep the TitleArray to show in tableView title and detailsArray to show in tableView subtitle.. once I start searching only title following my typing but subtitle nothing change.
#IBOutlet weak var AirportsTableView: UITableView!
var TitleArray = [String]()
var DetailsArray = [String]()
var NumberOfRows = 0
var filteredNamesArray = [String]()
var filteredDetailsArray = [String]()
var resultSearchController = UISearchController!()
**override func viewDidLoad() {
super.viewDidLoad()**
// Do any additional setup after loading the view.
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.resultSearchController.loadViewIfNeeded()
self.AirportsTableView.tableHeaderView = self.resultSearchController.searchBar
self.AirportsTableView.reloadData()
parseJSON()
}
func parseJSON() {
if let path = NSBundle.mainBundle().pathForResource("airports", ofType: "json") {
do {
let data = try NSData(contentsOfURL: NSURL(fileURLWithPath: path), options: NSDataReadingOptions.DataReadingMappedIfSafe)
let jsonObj = JSON(data: data)
if jsonObj != JSON.null {
// print("jsonData:\(jsonObj)")
NumberOfRows = jsonObj.count
for i in 0...NumberOfRows {
let City = jsonObj[i]["city"].string as String!
let Country = jsonObj[i]["country"].string as String!
let Iata = jsonObj[i]["iata"].string as String!
let Name = jsonObj[i]["name"].string as String!
self.TitleArray.append("\(City) - \(Country) - \(Iata)")
self.DetailsArray.append("\(Name)")
}
} else {
print("could not get json from file, make sure that file contains valid json.")
}
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("Invalid filename/path.")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if self.resultSearchController.active
{
return self.filteredNamesArray.count
} else
{
return self.TitleArray.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell?
if self.resultSearchController.active
{
cell!.textLabel?.text = self.filteredNamesArray[indexPath.row]
} else
{
cell!.textLabel?.text = self.TitleArray[indexPath.row]
cell!.detailTextLabel?.text = self.DetailsArray[indexPath.row]
}
return cell!
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filteredNamesArray.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.TitleArray as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.filteredNamesArray = array as! [String]
self.AirportsTableView.reloadData()
}
// MARK: - Segues
/*
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "AirportDetails" {
if let indexPath = self.AirportsTableView.indexPathForSelectedRow {
let airportDetail : Airports = TitleArray[indexPath.row]
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! AllWaysFlightsViewController
controller.airportDetail = airportDetail
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
*/
Instead of using two separate arrays use only one array and populate it with object containing both variables you are using to populate the tableView.
class Address {
var city: String
var detail: String
init(city: String, detail:String) {
self.city = city
self.detail = detail
}
}
Parse your json like this:
for i in 0...NumberOfRows {
let City = jsonObj[i]["city"].string as String!
let Country = jsonObj[i]["country"].string as String!
let Iata = jsonObj[i]["iata"].string as String!
let Name = jsonObj[i]["name"].string as String!
let city = "\(City) - \(Country) - \(Iata)"
let address = Address(city: city, detail: Name)
self.TitleArray.append(address)
self.filteredNamesArray.append(address)
}
Filter your title array containing addresses. Your titlearray and filtered array both contains same data for the first time you can refer to the json parsing for this. Here you can use one for filtering and when search bar is empty it user cancel his search you can re-populate your array from the other one.
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filteredNamesArray.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF.city CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.TitleArray as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.filteredNamesArray = array as! [Address]
self.AirportsTableView.reloadData()
}
your tableView logic will be changed accordingly
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.filteredNamesArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell?
let address = self.filteredNamesArray[indexPath.row]
cell!.textLabel?.text = address?.city
cell!.detailTextLabel?.text = address?.detail
return cell!
}
You need to change the way you approach filtering the data so that rather than just apply a predicate you explicitly iterate and check the predicate, if you find a match then you take that item and the corresponding description into your filtered arrays.
Something like:
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filteredNamesArray.removeAll(keepCapacity: false)
self.filteredDetailsArray.removeAll(keepCapacity: false)
let searchString = searchController.searchBar.text!
var index = 0
for title in self.TitleArray
if title.rangeOfString(searchString).location != NSNotFound {
self.filteredNamesArray.append(title)
self.filteredDetailsArray.append(self.DetailsArray[index])
}
index += 1
}
self.AirportsTableView.reloadData()
}
Related
I've been going round in circles trying to implement search functionality in my UITableView.
I've followed loads of instructions here on stackoverflow and watched a ton of videos on YouTube and I feel like I know how to implement UISearchController now but i'm struggling how to actually filter my array in the updateSearchResultsForSearchController function.
I've managed to get search working if I have a simple array of string values as you see in most of the online examples for implementing search but I have an array of dictionaries and have no idea how to use .filter to get at the key/value pairs in the dictionary.
My peopleData array is an array of dictionaries that comes from a JSON file which looks like this:
{
"people": [
{
"ID" : "1",
"firstname" : "Bob",
"lastname" : "Smith",
"age" : 25,
"gender" : "Male"
},
{
"ID" : "2",
"firstname" : "Fred",
"lastname" : "Smith",
"age" : "52",
"gender" : "Male"
}
]
}
My view controller looks like this:
// Created by Elliot Wainwright on 26/02/2016.
// Copyright © 2016 Elliot Wainwright. All rights reserved.
import UIKit
class SelectPersonTableViewController: UITableViewController {
var myList:NSMutableArray = []
var personData:NSMutableArray = []
var filteredPeopleData:NSMutableArray = []
var searchController : UISearchController!
var resultsController = UITableViewController()
#IBOutlet var selectPersonTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
guard let path = NSBundle.mainBundle().pathForResource("PeopleData", ofType: "json") else {
print("error finding file")
return
}
do {
let data: NSData? = NSData(contentsOfFile: path)
if let jsonResult: NSDictionary =
try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary {
personData = jsonResult["people"] as! NSMutableArray
}
} catch let error as NSError {
print("Error:\n \(error)")
return
}
self.resultsController.tableView.dataSource = self
self.resultsController.tableView.delegate = self
self.searchController = UISearchController(searchResultsController: self.resultsController)
self.tableView.tableHeaderView = self.searchController.searchBar
self.searchController.searchResultsUpdater = self
definesPresentationContext = true
selectPersonTable.reloadData()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
//Filter through the array
//---------------------------------------------
//I have no idea how to do this for my array!!!
//---------------------------------------------
//Update the results TableView
self.resultsController.tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.tableView {
return self.peopleData.count
} else {
return self.filteredPeopleData.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
if tableView == self.tableView {
for person in personData {
let thisPerson = personData.objectAtIndex(indexPath.row)
cell.textLabel?.text = thisPerson["person"] as? String
}
} else {
for person in filteredPeopleData {
let thisPerson = filteredPeopleData.objectAtIndex(indexPath.row)
cell.textLabel?.text = thisPerson["person"] as? String
}
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
myList.addObject(personData.objectAtIndex(indexPath.row))
navigationController?.popViewControllerAnimated(true)
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var vc = segue.destinationViewController as! ViewController
vc.peopleList = myList
}
}
As you can see, In ViewDidLoad I get the content of the JSON file within 'people' personData = jsonResult["people"] as! NSMutableArray so I end up with an array of dictionaries.
Ideally, users would be able to type something in the UISearchBar and the array (and so the table) would be filtered to show any elements that contain any values that include part of what the user has typed. So typing "ith" would return both rows as 'ith' appears in 'Smith' for both elements in the array.
At very least, i'd like to be able to search on at least one key/value.
Try this:
let results = personData.filter({ person in
if let firstname = person["firstname"] as? String, lastname = person["lastname"] as? String, query = searchController.searchBar.text {
return firstname.rangeOfString(query, options: [.CaseInsensitiveSearch, .DiacriticInsensitiveSearch]) != nil || lastname.rangeOfString(query, options: [.CaseInsensitiveSearch, .DiacriticInsensitiveSearch]) != nil
}
return false
})
filteredPeopleData = NSMutableArray(array: results)
This filters people that have a matching firstname property. You could implement something similar for lastname.
In my app I have two table views. The first table view has a set number of cells. These cells will always be the same and will never change The above table view will always have the 4 cells and never more. On my server I have my API which has routes for each of these cells.
For example:
GET - myAPI/Air
GET - myAPI/history
GET - myAPI/train
GET - myAPI/taxi
And each routes send backs different data
mainTablewView:
import UIKit
enum NeededAPI {
case Air
case History
case Train
case Taxi
}
class mainTableViewController : UITableViewController {
struct WeatherSummary {
var id: String
}
var testArray = NSArray()
var manuArray = NSArray()
// Array of sector within our company
var selectSector: [String] = ["Air", "History","Train","Taxi"]
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = 80.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.selectSector.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("sectorList", forIndexPath: indexPath)
// Configure the cell...
if selectSector.count > 0 {
cell.textLabel?.text = selectSector[indexPath.row]
}
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "AirSegue"){
if let destination = segue.destinationViewController as? AirTableViewController {
let indexPath:NSIndexPath = self.tableView.indexPathForSelectedRow!
if let row:Int = indexPath.row {
destination.apiThatNeedsToBeCalled = .Air
}
}
}
if (segue.identifier == "HistorySegue"){
if let destination = segue.destinationViewController as? HistoryTableViewController {
let indexPath:NSIndexPath = self.tableView.indexPathForSelectedRow!
if let row:Int = indexPath.row {
destination.apiThatNeedsToBeCalled = .History
}
}
}
if (segue.identifier == "TrainSgue"){
if let destination = segue.destinationViewController as? TrainTableViewController {
let indexPath:NSIndexPath = self.tableView.indexPathForSelectedRow!
if let row:Int = indexPath.row {
destination.apiThatNeedsToBeCalled = .Train
}
}
}
if (segue.identifier == "TaxiSegue"){
if let destination = segue.destinationViewController as? TaxiTableViewController {
let indexPath:NSIndexPath = self.tableView.indexPathForSelectedRow!
if let row:Int = indexPath.row {
destination.apiThatNeedsToBeCalled = .Taxi
}
}
}
}
}
and Post
import Foundation
class Post : CustomStringConvertible {
var userId:Int
var title: String
init(userid:Int , title:String){
self.userId = userid
self.title = title
}
var description : String { return String(userId) }
}
When user selects cell you set the correct value for the apiThatNeedsToBeCalled. Once you do this, code inside the didSet will get executed and it should call the function which calls the appropriate API.
to other tableView :
import UIKit
class AirTableViewController: UITableViewController {
var postCollection = [Post]()
var apiThatNeedsToBeCalled:NeededAPI = .Air {
didSet {
//check which API is set and call the function which will call the needed API
AirLine()
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var apiThatNeedsToBeCalled:NeededAPI = .Air {
didSet {
//check which API is set and call the function which will call the needed API
AirLine()
}
}
func AirLine(){
let url = NSURL(string: "http://jsonplaceholder.typicode.com/posts")
NSURLSession.sharedSession().dataTaskWithURL(url!){[unowned self] (data , respnse , error) in
if error != nil{
print(error!)
}else{
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [[String:AnyObject]]
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
var newPost = Iduser(id: 0)
for posts in json {
let postObj = Post(userid:posts["userId"] as! Int,title: posts["title"] as! String)
self.postCollection.append(postObj)
}
dispatch_async(dispatch_get_main_queue()){
self.tableView.reloadData()
}
}catch let error as NSError{
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
print(error.localizedDescription)
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON:\(jsonStr)")
dispatch_async(dispatch_get_main_queue()) {
let alert = UIAlertController(title: "Alert", message: "Oops! Wrong Details, Try Again", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return self.postCollection.count ?? 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("AirCell", forIndexPath: indexPath)
// Configure the cell...
// cell.textLabel?.text = "test"
let weatherSummary = postCollection[indexPath.row]
cell.textLabel?.text = String(weatherSummary.userId)
cell.detailTextLabel?.text = weatherSummary.title
return cell
}
}
mainTableView and Air cell is Ok but when that selected other return The same information Air cell?
Perhaps I'm just missing it, but I can see your creation of the NSURLSession looks fine, but I don't see where you're calling .resume() on that once you've created it. If you don't call .resume() it'll never even perform that URLSession at all. Check the discussion here.
import UIKit
class MasterTableViewController: UITableViewController, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating {
//approches for uisearchbar
var searchNotes: [PFObject] = [PFObject]()
var notesSearchController = UISearchController()
var searchActive: Bool = false
// creating array for holding ojects
var noteObjects: NSMutableArray! = NSMutableArray()
var v = 0
override func viewDidLoad() {
super.viewDidLoad()
self.notesSearchController = UISearchController(searchResultsController: nil)
self.notesSearchController.dimsBackgroundDuringPresentation = true
self.notesSearchController.searchResultsUpdater = self
// Configure the search controller's search bar
self.notesSearchController.searchBar.placeholder = "Search for a user"
self.notesSearchController.searchBar.sizeToFit()
self.notesSearchController.searchBar.delegate = self
self.definesPresentationContext = true
// Set the search controller to the header of the table
self.tableView.tableHeaderView = self.notesSearchController.searchBar
print("check")
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if v == 0 {
self.fetchAllObjectsFromLocalDataStore()
//self.fetchAllObjects()
}
}
// fetching data from local datastore and from parse
func fetchAllObjectsFromLocalDataStore(){
let query: PFQuery = PFQuery(className: "Sinhgad")
query.orderByDescending("createdAt")
query.fromLocalDatastore()
query.findObjectsInBackgroundWithBlock { ( objects, error) -> Void in
if (error == nil) {
let temp: NSArray = objects as NSArray!
self.noteObjects = temp.mutableCopy() as! NSMutableArray
self.tableView.reloadData()
}else {
print(error!.userInfo)
}
}
}
func fetchAllObjects(){
let query: PFQuery = PFQuery(className: "Sinhgad")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
PFObject.pinAllInBackground(objects, block: nil )
self.fetchAllObjectsFromLocalDataStore()
// self.tableView.reloadData()
} else {
print(error?.userInfo)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(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
if (self.notesSearchController.active) {
return self.searchNotes.count
} else {
return self.noteObjects.count
}}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MasterTableViewCell
if (self.notesSearchController.active && self.searchNotes.count > indexPath.row) {
// bind data to the search results cell
let object : PFObject = self.noteObjects.objectAtIndex(indexPath.row) as! PFObject
cell.MasterTitleLabel?.text = object["Title"] as? String
cell.MasterTextLabel.text = object["Fstory"] as? String
cell.MasterTimeLabel.text = object["Time"] as? String
cell.MasterLocationLabel.text = object["Location"] as? String
return cell
} else {
let object : PFObject = self.noteObjects.objectAtIndex(indexPath.row) as! PFObject
cell.MasterTitleLabel?.text = object["Title"] as? String
cell.MasterTextLabel.text = object["Fstory"] as? String
cell.MasterTimeLabel.text = object["Time"] as? String
cell.MasterLocationLabel.text = object["Location"] as? String
return cell
}}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (self.notesSearchController.active && self.searchNotes.count > 0) {
// Segue or whatever you want
self.performSegueWithIdentifier("openStory", sender: self)
} else {
self.performSegueWithIdentifier("openStory", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let upcoming: AddNoteTableViewController = segue.destinationViewController as! AddNoteTableViewController
if (segue.identifier == "openStory"){
let indexPath = self.tableView.indexPathForSelectedRow!
let object: PFObject = self.noteObjects.objectAtIndex(indexPath.row) as! PFObject
upcoming.object = object
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
#IBAction func btnReload(sender: AnyObject) {
fetchAllObjects()
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete ){
let object : PFObject = self.noteObjects.objectAtIndex(indexPath.row) as! PFObject
// the below for deleting the selected cell's object from server's database
// object.deleteInBackground()
//the below for deleting the selected cell's object from localstorage
object.unpinInBackground()
self.noteObjects.removeObjectAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
// MARK: - Parse Backend methods
func loadSearchUsers(searchString: String) {
let query: PFQuery = PFQuery(className: "Sinhgad")
query.orderByDescending("createdAt")
// Filter by search string
query.whereKey("Notes", containsString: searchString)
self.searchActive = true
query.findObjectsInBackgroundWithBlock {(objects, error) -> Void in
if (error == nil) {
self.searchNotes.removeAll(keepCapacity: false)
self.searchNotes += objects as [PFObject]!
self.tableView.reloadData()
} else {
// Log details of the failure
print("search query error: \(error) \(error!.userInfo)")
}
self.searchActive = false
}
}
// MARK: - Search Bar Delegate Methods
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
// Force search if user pushes button
let searchString: String = searchBar.text!.lowercaseString
if (searchString != "") {
loadSearchUsers(searchString)
}
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
// Clear any search criteria
searchBar.text = ""
// Force reload of table data from normal data source
}
// MARK: - UISearchResultsUpdating Methods
// This function is used along with UISearchResultsUpdating for dynamic search results processing
// Called anytime the search bar text is changed
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchString: String = searchController.searchBar.text!.lowercaseString
if (searchString != "" && !self.searchActive) {
loadSearchUsers(searchString)
}
}
}
The above code is for retrieving stored objects from parse's server and from local storage and show them in table view.
Everything is working fine but I am trying to implement searchbar for adding searching function into my app. The problem is that when am running the app its showing the searchbar but when interacting with search bar its moving to upside and disappearing and when am typing anything.
I am not getting any search result and in NSLog am getting this :
2015-12-03 16:43:48.769 Notes[1015:56944] Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UISearchController: 0x7ff2d48165a0>)
I know am missing something and its not the right way to achieve that function.
If somebody knows how to do it correctly or what am missing than please let me know , thanks and sorry if the way am asking question is not proper !
i just figured out that my 'searchNotes' var of Pfobject have nothing i mean its empty ! for that i tried
cell.MasterTitleLabel?.text = searchNotes["Title"] as! String
but its giving error
cannot subscript a value of type '[PFObject]' with an index of type 'string'
i know its because i declared searchNotes as
searchNotes [PFObject] = [PFObject]()
i should do it something like
searchNotes PFObject = PFObject()
but when am doing this its giving so many errors please help if somebody's know how to fix this
Maybe you shouldn't user PFObject directly.
Can you use another class instead of PFObject
private class object {
var mTitle : String!
var mStory : String!
var mTime : String!
var mLocation : String!
}
And use your code here
let obj : object = object()
obj.mTitle = PFObject["title"];
...etc
The old UISearchDisplayController class is now deprecated and instead we have to use the new UISearchController. There used to be a property in the old class called "SearchResultsTableView" but it's gone from the new class.
I populate a table with data and all works as intended - including segueing each row's details to another scene. I throw a search bar in there (programmatically - using the new searchController) and it successfully reloads the original table with any found results.
HOWEVER, when touching a selected row after a search, the segue passed along is that of the original table row that happens to be in the same position of the one touched now! (i.e. if I choose the current second row of a search, the next scene will segue the details of the second row of the original table!) That's because despite the data in the rows are being successfuly repopulated with the search data, the index numbers are still those of the old data.
It used to be with the old type that we would check this as such:
if (self.resultSearchController.active) {
let indexPath = self.searchDisplayController!.searchResultsTableView.indexPathForSelectedRow()
} else {
let indexPath = self.tableView.indexPathForSelectedRow()
So I think that with the old UISearchDisplayController class you actually got a new table, whereas with the new SearchController Class you only get new rows inside the old table? This totaly doesn't make sense !
Here is my full code per request:
import UIKit
import Foundation
class secondTableViewController: UITableViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {
var filteredTableData = [String]()
var resultSearchController = UISearchController()
//these 2 are standard for the title and subtitle
var TableTitle:Array< String > = Array < String >()
var TableSub:Array< String > = Array < String >()
//the following are for my seque to next scene
var the_fname:Array< String > = Array < String >()
var the_basics:Array< String > = Array < String >()
var the_p_method:Array< String > = Array < String >()
var the_seats:Array< String > = Array < String >()
var the_notes:Array< String > = Array < String >()
var the_tableData:Array< String > = Array < String >()
override func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
self.title = currentBus
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
// Reload the table
self.tableView.reloadData()
var url = "http://the_path_to_my_json_file"
get_data_from_url(url)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// 2
if (self.resultSearchController.active) {
return self.filteredTableData.count
}
else {
return TableTitle.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("secondtableCell", forIndexPath: indexPath) as! UITableViewCell
// Configure the cell...
if (self.resultSearchController.active) {
cell.textLabel?.text = filteredTableData[indexPath.row]
//cell.detailTextLabel?.text = TableSub[indexPath.row]
}else{
cell.textLabel?.text = TableTitle[indexPath.row]
cell.detailTextLabel?.text = TableSub[indexPath.row]
}
return cell
}
func get_data_from_url(url:String)
{
let httpMethod = "GET"
let timeout = 15
let url = NSURL(string: url)
let urlRequest = NSMutableURLRequest(URL: url!,
cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let queue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(
urlRequest,
queue: queue,
completionHandler: {(response: NSURLResponse!,
data: NSData!,
error: NSError!) in
if data.length > 0 && error == nil{
let json = NSString(data: data, encoding: NSASCIIStringEncoding)
self.extract_json(json!)
}else if data.length == 0 && error == nil{
println("Nothing was downloaded")
} else if error != nil{
println("Error happened = \(error)")
}
}
)
}
func extract_json(data:NSString)
{
var parseError: NSError?
let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &parseError)
if (parseError == nil)
{
if let my_pass_list = json as? NSArray
{
for (var i = 0; i < my_pass_list.count ; i++ )
{
if let each_pass = my_pass_list[i] as? NSDictionary
{
if let fname = each_pass["fname"] as? String
{
if let lname = each_pass["lname"] as? String
{
if let numofseats = each_pass["numofseats"] as? String
{
if let showed_up = each_pass["showed_up"] as? String
{
if let res_id = each_pass["resnum"] as? String
{
if let res_notes = each_pass["res_notes"] as? String
{
if let payment_description = each_pass["payment_description"] as? String
{
// the_tableData.append(fname)
the_fname.append(fname)
the_basics.append(fname + " " + lname)
the_p_method.append(payment_description)
the_seats.append(numofseats)
the_notes.append(res_notes)
TableTitle.append(fname + " " + lname)
TableSub.append("Seats Reserved: " + numofseats + ". Showed Up: " + showed_up + ". Notes:" + res_notes)
the_tableData = TableTitle
}
}
}
}
}
}
}
}
}
}
}
do_table_refresh();
}
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
return
})
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
var thirdScene = segue.destinationViewController as! customer_details_View_Controller
if let indexPath = self.tableView.indexPathForSelectedRow() {
/*
so what I'm missing is to be able to check
if (self.resultSearchController.active) {
and if yes have indexPath be the self.resultSearchController.resultSearchTableView.indexPathForSelectedRow() {
or something of that nature
*/
thirdScene.dotrav = todayString
thirdScene.from = currentBus
thirdScene.basics = the_basics[indexPath.row]
thirdScene.p_method = the_basics[indexPath.row]
thirdScene.seats = the_tableData[indexPath.row]
thirdScene.notes = the_notes[indexPath.row]
}
// Pass the selected object to the new view controller.
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text)
let array = (the_tableData as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
}
You need to account for the fact that you are going to have different data in your tableView depending on the search result. You can still use self.tableView.indexPathForSelectedRow.
What I do, is keep a reference to my base data, and then keep a reference to my filtered data, and display my filtered data in the tableView at all times. If my searchBar has no text, then my filtered data is equal to my base data.
Example:
class MyTableViewController: UITableViewController, UISearchResultsUpdating {
var data: [String] = ["One", "Two", "Three", "Four", "Five"]
var filteredData: [String]!
var searchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
setUpSearchController()
setFilteredDataForCurrentSearch()
}
private func setUpSearchController() {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
self.tableView.tableHeaderView = searchController.searchBar
}
private func setFilteredDataForCurrentSearch() {
if let searchString = searchController.searchBar.text where !searchString.isEmpty {
filteredData = data.filter({ (string: String) -> Bool in
return searchString.rangeOfString(string, options: NSStringCompareOptions.CaseInsensitiveSearch) != nil
})
} else {
filteredData = data
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
setFilteredDataForCurrentSearch()
}
}
Now, you can implement all of your UITableViewDataSource and UITableViewDelegate methods using the filteredData.
In prepareForSegue, you retrieve the correct selected object like:
let indexPath = tableView.indexPathForSelectedRow()
let selectedObject = filteredData[indexPath.row]
I am loading a UITableViewController with data from my Contact List. Everything works fine in simulator with the small set of data, but on my iPhone with 82 contacts, i get a varying number of rows loaded, from 0, to most of the data, but never all of the data, and always a different amount. It's almost as though the table is being loaded and displayed before the data array is complete. If i set the numberOfRowsInSection return manually to 82 it works fine, but when set to contactdatalist.count it gets a lesser number. Is there something i am doing wrong, or can i slow down the tableview load until all the data load is complete? It seems asynchronous in operation.
import UIKit
import AddressBook
class TableViewControllerContacts: UITableViewController {
var contactdatalist = [ContactData]()
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
self.contactdatalist = []
let addressBook : ABAddressBookRef? = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()
ABAddressBookRequestAccessWithCompletion(addressBook, { (granted : Bool, error: CFError!) -> Void in
if granted == true {
let allContacts : NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue()
for contactRef:ABRecordRef in allContacts { // first name
let myPBfirstname = ABRecordCopyValue(contactRef, kABPersonFirstNameProperty)?.takeRetainedValue() as! NSString? ?? ""
let myPBlastname = ABRecordCopyValue(contactRef, kABPersonLastNameProperty)?.takeRetainedValue() as! NSString? ?? ""
let phonesRef: ABMultiValueRef = ABRecordCopyValue(contactRef, kABPersonPhoneProperty)?.takeRetainedValue() as ABMultiValueRef? ?? ""
var phonesArray = Array<Dictionary<String,String>>()
for var i:Int = 0; i < ABMultiValueGetCount(phonesRef); i++ {
let myPhLabel = ABMultiValueCopyLabelAtIndex(phonesRef, i)?.takeRetainedValue() as NSString? ?? ""
let myPhValue = ABMultiValueCopyValueAtIndex(phonesRef, i)?.takeRetainedValue() as! NSString? ?? ""
if myPhLabel.containsString("Mobile") {
self.contactdatalist.append(ContactData(firstname:myPBfirstname as String, lastname:myPBlastname as String, phone:myPhValue as String))
}
}
}
}
})
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return self.contactdatalist.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
// Configure the cell...
let contactdata = self.contactdatalist[indexPath.row]
cell.textLabel!.text = ("\(contactdata.firstname) \(contactdata.lastname)")
return cell
}
Update your viewDidLoad, so that contacts are fetched in a separate thread, and tableview is loaded when the contacts are fetched.
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
self.contactdatalist = []
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
let addressBook : ABAddressBookRef? = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()
ABAddressBookRequestAccessWithCompletion(addressBook, { (granted : Bool, error: CFError!) -> Void in
if granted == true {
let allContacts : NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue()
for contactRef:ABRecordRef in allContacts { // first name
let myPBfirstname = ABRecordCopyValue(contactRef, kABPersonFirstNameProperty)?.takeRetainedValue() as! NSString? ?? ""
let myPBlastname = ABRecordCopyValue(contactRef, kABPersonLastNameProperty)?.takeRetainedValue() as! NSString? ?? ""
let phonesRef: ABMultiValueRef = ABRecordCopyValue(contactRef, kABPersonPhoneProperty)?.takeRetainedValue() as ABMultiValueRef? ?? ""
var phonesArray = Array<Dictionary<String,String>>()
for var i:Int = 0; i < ABMultiValueGetCount(phonesRef); i++ {
let myPhLabel = ABMultiValueCopyLabelAtIndex(phonesRef, i)?.takeRetainedValue() as NSString? ?? ""
let myPhValue = ABMultiValueCopyValueAtIndex(phonesRef, i)?.takeRetainedValue() as! NSString? ?? ""
if myPhLabel.containsString("Mobile") {
self.contactdatalist.append(ContactData(firstname:myPBfirstname as String, lastname:myPBlastname as String, phone:myPhValue as String))
}
}
}
}
})
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
Requesting the address book is an asynchronous operation. So you should reload the data in the table view inside that block at last step, and not at the end of viewDidLoad.