I m using a UISearchController to let the user search though my data (these comes from XML and been parsed to arrays), now i want to add more then one UILabel into my Cell, but these UILabel Information comes from different arrays.
As soon as i update my Search, the 2. array does not get sorted the same way like the first does.
AP = ["FIRST1","FIRST2","FIRST3","FIRST4","FIRST5"]
APSecound = ["SEC1","SEC2","SEC3","SEC4","SEC5"]
var filteredTableDataAP = [String]()
var filteredTableDataAPSecound = [String]()
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredTableDataIcao.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (AP as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableDataAP = array as! [String]
//i think here i some how could set index of "filteredTableDataAP = filteredTableDataAPSecound" so they been filtered like the same shema
self.tableView.reloadData()
}
Override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCellSearch", forIndexPath: indexPath) as! CustomTableViewCellSearch
if (self.resultSearchController.active) {
cell.APLB.text = filteredTableDataAP[indexPath.row]
cell.APSecoundLB.text = filteredTableDataAPSecound[indexPath.row]
return cell
}
else {
cell.APLB.text = AP[indexPath.row]
cell.APSecoundLB.text = APSecound[indexPath.row]
return cell
}
}
On first run without searching the cell looks fine like
(FIRST1)(SEC1)
(FIRST2)(SEC2)
(FIRST3)(SEC3)
(FIRST4)(SEC4)
But once i search for "4" the cell looks like
(FIRST4)(SEC1)
is there any easy way to "keep the position of the second array like the one of the first"
The way i have used is below
you have two array :
AP = ["FIRST1","FIRST2","FIRST3","FIRST4","FIRST5"]
APSecound = ["SEC1","SEC2","SEC3","SEC4","SEC5"]
var getIndex = 0
if (self.SearchController.active) // check if SearchController isactive
{
//This will get the index of main array
getIndex = AP.indexOf(filteredsearchPost[indexPath.row])!
//According to example index should be 1 for "FIRST2"
. cell.APSecoundLB.text = APSecound[getIndex]
//set it with the retrived index and its done
//it will print "SEC2"
}else {
getIndex = indexPath.row
cell.APLB.text = AP[getIndex]
cell.APSecoundLB.text = APSecound[getIndex]
//Do the else part here
}
you just filtered the AP not filtered APSecound
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (AP as NSArray).filteredArrayUsingPredicate(searchPredicate)
let arraySecond = (APSecound as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableDataAP = array as! [String]
filteredTableDataAPSecound = arraySecond as! [String]
or you can use filteredResault: [[String: String?]] , [String: String?] is the Dictionary, filteredResault is Dictionary array. dict like ["AP": "FIRST1", "APSecound": "SEC1"]
Related
hello I have implemented an auto complete search on my app. I have cities stored in my mysql database and in app when user types any character or word, the app fetches result from the database and shows it. The problem which I am having is there are more then 1000 cities stored in database and when user lets say type one character my app keyboard got stuck a little and it takes a lot of memory while it fetches the result and shows it. Is there any better way to implement this kind a functionality. Please Please look at my code and let me know what changes should I done in my code
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterTableData.removeAll(keepCapacity: false)
let searchWord = searchController.searchBar.text!
getCityNamesFromServer(searchWord)
let searchPredict = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
for var i = 0; i < self.dict.count; i++ {
let cityname = (((self.dict["\(i)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
newTableData.append(cityname)
}
let array = (newTableData as NSArray).filteredArrayUsingPredicate(searchPredict)
print("array is\(array)")
filterTableData = array as! [String]
self.tableView.reloadData()
}
Full Code:
class CityTableViewController: UITableViewController, UISearchResultsUpdating {
var dict = NSDictionary()
var filterTableData = [String]()
var resultSearchController = UISearchController()
var newTableData = [String]()
override func viewDidLoad() {
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
})()
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(self.resultSearchController.active){
return self.filterTableData.count
}else {
return dict.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CountryTableViewCell
if(self.resultSearchController.active){
cell.cityNameLabel.text = filterTableData[indexPath.row]
return cell
}else{
cell.cityNameLabel.text = (((self.dict["\(indexPath.row)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterTableData.removeAll(keepCapacity: false)
let searchWord = searchController.searchBar.text!
getCityNamesFromServer(searchWord)
let searchPredict = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
print("searchPredict is \(searchController.searchBar.text!)")
for var i = 0; i < self.dict.count; i++ {
let cityname = (((self.dict["\(i)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
newTableData.append(cityname)
}
let array = (newTableData as NSArray).filteredArrayUsingPredicate(searchPredict)
print("array is\(array)")
filterTableData = array as! [String]
self.tableView.reloadData()
}
func getCityNamesFromServer(searchWord:String){
let url:String = "http://localhost/"
let params = ["city":searchWord]
ServerRequest.postToServer(url, params: params) { result, error in
if let result = result {
print(result)
self.dict = result
}
}
}
}
The best way to limit the amount of data is returned by adding a LIMIT clause to your query. Example SQL query:
SELECT name FROM cities WHERE name like '%something%';
Change this to:
SELECT name FROM cities WHERE name like '%something%' LIMIT 10;
From the user point of view it does not make sense to return 1000 rows, you would not be able to display it anyways, therefore you have to come up with a number that fits on your screen. After any additional key press you can repeat the query with the updated search string.
In my UITableView I have cells with avatar image and name surname(UIImage and Label). And I want to search inside of my UITableView. So, when I type my user's name or surname it would show user's name surname with the avatar image.
Now, I can search just inside of an array with the next code:
var rosters = [String: UIImage]()
var displayNames = [String]()
var filteredTableData = [String]()
// Here I remove all elements from the array and add to it user display name and avatar
rosters.removeAll(keepCapacity: false)
for user in OneRoster.buddyList.fetchedObjects! {
var roster = OneRoster.userFromRosterForJID(jid: user.jidStr)
var image = OneChat.sharedInstance.xmppvCardAvatarModule?.photoDataForJID(roster?.jid)
rosters[user.displayName] = UIImage(data: image!)!
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> onlineUserCell {
let cell:onlineUserCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! onlineUserCell
if (self.resultSearchController.active) {
cell.username.text = filteredTableData[indexPath.row]
//cell.avatarImage.image = images[indexPath.row]
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredTableData.removeAll(keepCapacity: false)
displayNames.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
for (name, image) in rosters {
displayNames.append(name)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (displayNames as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
}
let array = (displayNames as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
So, this code searches just among name surname, without avatar image. I thought, that I can use dictionary, as:
var users = [UIImage: String]()
and later search by String and show as UIImage and String together in the cell. But I searched a lot and couldn't found how to search, that later I can search by String and to get result with UIImage, too.
Any ideas?
Searching the dictionary could be done by just accessing the values from the dictionary and searching them like
var users = [UIImage: String]()
let values = Array(users.values)
However this use of dictionary seems a bit wrong as you will never look up a value based on the UIImage I guess.
What you should use instead then would be a tuple or a class that holds both UIImage and String
var users = [(image:UIImage,name:String)]()
you can then simply filter the values like
users.filter { (element:(image:UIImage, name:String)) -> Bool in
return element.name.containsString(searchfrase)
}
I get it to load, I get the sections to load, but I cant get the cells in each section to load correctly. It restarts from the beginning in each section effectively duplicating each cell. My entity name is Customer and it holds the following attributes. firstName, lastName, phoneNumber,email, notes, and first. First is the first letter in the last name to use for the sections. I can get it to load, the headers load successfully but as soon as I start adding names with the same first letter in the last name, it starts messing up. Let me know if you need any more code.
override func tableView(tableView: UITableView,
cellForRowAtIndexPath
indexPath: NSIndexPath?) -> UITableViewCell
{
//getSections()
let cell =
tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath!)
let cust = customers[(indexPath?.section)!]
print(indexPath?.row)
let fName = cust.valueForKey("firstName") as? String
print(fName)
let lName = cust.valueForKey("lastName") as? String
print(lName)
cell.textLabel!.text = fName! + " " + lName!
print("Cell: \(cell.textLabel!.text)")
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
context = managedContext
//2
let fetchRequest = NSFetchRequest(entityName:"Customer")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "lastName", ascending: true)]
//3
//var error: NSError?
let fetchedResults = try!
managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
if var results = fetchedResults
{
customers = results
}
else
{
print("Could not fetch")
}
tableView.reloadData()
getSections()
}
func getSections()
{
var sections:[String] = []
for(var i=0;i<customers.count;i++)
{
let cust = customers[i]
sections.append(cust.valueForKey("first") as! String)
}
sectionHeadersTotal = sections
let unique = Array(Set(sections))
sectionHeaders = unique.sort()
print(sectionHeaders.count)
//sectionHeaders = unique
//return unique.count
}
Take a look here for the approach to read section-wise data -
http://www.andrewcbancroft.com/2015/03/05/displaying-data-with-nsfetchedresultscontroller-and-swift/
and here
iOS UITableView sections with fetchedResultsController confusion
i've created a tableView which i first did set to that it should use the filtered array if the searchController was active. However i want to first do it when the searchBar contain more than 0 characters. However this does not seem to work. Here is what i've done so far in cellForRowAtIndexPath
if (count(self.teamSearchController.searchBar.text) > 0) {
team = filteredTableData[indexPath.row] as Team
cell.textLabel?.text = team.name as String
} else {
team = self.teamArray[indexPath.row] as Team
cell.textLabel?.font = UIFont(name: "HelveticaNeue-Light", size: 20)
cell.textLabel?.text = team.name as String
}
updateSearchResultsForSearchController
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "name CONTAINS[c] %# OR shortname CONTAINS[c] %#", searchController.searchBar.text, searchController.searchBar.text)
let array = (teamArray as NSArray).filteredArrayUsingPredicate(searchPredicate) as! [Team]
filteredTableData = array
self.tableView.reloadData()
}
You should do the checking for length of search text in updateSearchResultsForSearchController
func updateSearchResultsForSearchController(searchController: UISearchController)
{
if searchController.searchBar.text == "" {
filteredTableData = self.teamArray
} else {
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "name CONTAINS[c] %# OR shortname CONTAINS[c] %#", searchController.searchBar.text, searchController.searchBar.text)
let array = (teamArray as NSArray).filteredArrayUsingPredicate(searchPredicate) as! [Team]
filteredTableData = array
}
self.tableView.reloadData()
}
Update the code in cellForRowAtIndexPath as shown below, just have a check if the searchController is active or not
if (self.teamSearchController.active) {
team = filteredTableData[indexPath.row] as Team
} else {
team = self.teamArray[indexPath.row] as Team
cell.textLabel?.font = UIFont(name: "HelveticaNeue-Light", size: 20)
}
cell.textLabel?.text = team.name as String
I think it is not good design to query the search bar in every table view cell. Instead, have only one array that the table view displays.
Whenever the search bar contents change, change the content of the array to be displayed in the table to the filtered array. Handle your edge case in the update method. I think the if statement you wrote should work fine.
I'm trying to create a search function using the UISearchController. However i cant seem to make it work with my Team Object. I've started by creating a Team Object which contain a id, name and shortname. Then i'm retrieving the teamData from a url and adding the Team Objects into an array which is populated into a tableView. This tableView contain a searchController which is suppose to filter the Data, but nothing happens.
arrays
var teamArray = Array<Team>()
var filteredTableData = [String]()
GetTeams function
func getTeams(url: String) {
isApiCalling = true
request(.GET, url, parameters: nil)
.response { (request, response, data, error) in
if error == nil {
let data: AnyObject = data!
let jsonArray = JSON(data: data as! NSData)
for (key: String, subJson: JSON) in jsonArray {
// Create an object and parse your JSON one by one to append it to your array
var newTeamObject = Team(id: subJson["id"].intValue, name: subJson["name"].stringValue, shortname: subJson["shortname"].stringValue)
self.teamArray.append(newTeamObject)
}
self.isApiCalling = false
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
}
}
CellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("teamCell", forIndexPath: indexPath) as! TeamCell
cell.textLabel?.font = UIFont(name: "HelveticaNeue-Light", size: 20)
cell.textLabel?.text = self.teamArray[indexPath.row].name as String
if (self.cellSelected.containsObject(indexPath)) {
cell.accessoryView = cell.accessoryCheck
} else {
cell.accessoryView = cell.accessoryUncheck
}
return cell
}
FilterData
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text)
let array = (teamArray as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
Team Objects
class Team{
var id: Int!
var name: NSString!
var shortname: NSString!
init(id: Int, name:NSString, shortname: NSString) {
self.id = id
self.name = name
self.shortname = shortname
}
}
The objects in the teamArray don't have a SELF property. You can't use SELF to search in all the properties of the object at once. You have to give the name of the property, and if you want to search in more than one you have to add all those properties to the predicate.
I would think it's enough for you to search in the name property like so:
let searchPredicate = NSPredicate(format: "name CONTAINS[c] %#", searchController.searchBar.text)
If you need in more properties you do like this:
let searchPredicate = NSPredicate(format: "name CONTAINS[c] %# OR shortname CONTAINS[c] %#", searchController.searchBar.text, searchController.searchBar.text)
Can you post the definition of your Team object, and any sub-objects that it contains? (TeamData).
Also indicate where you expect the search text to appear in your team object.
I haven't used NSPRedicate a lot, but my understanding of the CONTAINS comparison is that it checks an individual field to see if it contains a substring. I don't think it will check all fields of the objects you're searching. That seems like what you're expecting.
let searchPredicate = NSPredicate(format: "name contains[c] %#", searchWord)
self.filteredArray = self.array.filteredArrayUsingPredicate(searchPredicate)