Accessing data in filtered array - ios

I am implementing UISearchBar and I using filtered array where will be collected matching results. My problem is in TableView I do not know what is the proper way accessing items in "menuItemsFiltered" array. My goal is when searching for a title to display correct Image that assigned to that title. Please help!
//Declaring dict arrays:
var menuItems = [["Name" : "20/20 Cafe", "Image": "20_20Cafe.jpg"],
["Name": "Au Bon Pain", "Image": "AuBonPain.jpg"]]
var menuItemsFiltered = [(Name:String, Image:UIImage)]()
Here is my TableView
//Display info in our Residential cells
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("RetailCell", forIndexPath: indexPath) as! ResidentialTableViewCell
let entry = menuItems[indexPath.row]
let anotherEntry = menuItemsFiltered[indexPath.row]
if self.resultSearch.active
{ //Error in this block
cell.RetailLabel.text = menuItemsFiltered[indexPath.row]
//cell.RetailImage.image = anotherEntry
}
else
{ //All good here
cell.RetailImage.image = UIImage(named: entry["Image"]!)
cell.RetailLabel.text = entry["Name"]!
}
UPDATE: Changed from a dictionary to an array of Tuples:
var menuItems = [(name : "20/20 Cafe", image: "20_20Cafe.jpg"),
(name: "Au Bon Pain", image: "AuBonPain.jpg")]
var menuItemsFiltered: [(name: String, image: String)] = []
In my table view if / else statements
if self.resultSearch.active
{
cell.RetailLabel.text = menuItemsFiltered[indexPath.row].name
cell.RetailImage.image = UIImage(named: menuItemsFiltered[indexPath.row].image)
}
else
{
cell.RetailImage.image = UIImage(named: menuItems[indexPath.row].image)
cell.RetailLabel.text = menuItems[indexPath.row].name
}

Related

Index out of range for only last 2 values?

I am currently getting index out of range but this only happens after the first 3 items have been displayed successfully. I looked through the code and I still don't know where im going wrong. Index out of range happens only when trying to load text in the cells.
Log when method is called from viewdidload:
Description of indexPath: 0
Description of indexPath: 1
Description of indexPath: 2
Log when I press a button to load the rest of images:
Description of indexPath: 0
Description of indexPath: 1
Description of indexPath: 2
Description of indexPath: 3
Datasource method where indexpath is out of range:
//MARK: - tableview datasource methods
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if arrayOfImgObj.count > 0 {
cell.imageView?.image = arrayOfImgObj[indexPath.row].imageTempt
print("Description of indexPath: ",indexPath.row)
cell.textLabel?.text = arrayOfUIDs[indexPath.row] // index out of range
} else {
print("\(arrayOfImgObj.count) not over 0 yet")
}
return cell
}
Method that is called when viewDidload:
//testing array
var arrayOfUIDs = User().users
func fetchAllUserFristImage() {
Database.database().reference().child("Posts").observe(.childAdded, with: {(snapshot) in
if (snapshot.value as? [String: AnyObject]) != nil {
let user = User()
user.id = snapshot.key
//testing array
self.arrayOfUIDs.append(snapshot.key)
print("\(String(describing: user.id)) <-- SHUD BE THE USERUID")
self.databaseRef = Database.database().reference()
let usersPostRef2 = self.databaseRef.child("Posts").child(user.id!)
usersPostRef2.observe(.value, with: {(postXSnapshots) in
if let postDictionary2 = postXSnapshots.value as? NSDictionary {
for (p) in postDictionary2 {
let posts = p.value as! NSDictionary
//to get back to where i was delete the below for i
for (i) in posts {
let imageUrlString = i.value as! NSDictionary
guard let postUrl = imageUrlString.value(forKey: "image1") else {return}
//below is ting from below
if postUrl != nil {
self.feedArray.append(Post(fetchedImageURL: postUrl as! String))
let imageUrlString = "\(postUrl)"
let imageUrl = URL(string: imageUrlString)!
print("\(imageUrl) this shud be img url's of posts")
let imageDataL = try! Data(contentsOf: imageUrl)
self.imageObject.img2 = UIImage(data: imageDataL)
let image1ToDisplay: UIImage = self.imageObject.img2!
self.arrayOfImgObj.append(forTable(imageTempt: image1ToDisplay))
} else {print("this user had no posts, was nil")}
}
}
}
})
}
//below shud stay same
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
Edit:
I have changed teh code to only use one array of objects:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if arrayOfImgObj.count > 0 {
cell.imageView?.image = arrayOfImgObj[indexPath.row].imageTempt
print("Description of indexPath: ",indexPath.row)
cell.textLabel?.text = arrayOfImgObj[indexPath.row].users[indexPath.row] // index out of range
} else {
print("\(arrayOfImgObj.count) not over 0 yet")
}
return cell
}
changing one line in the function to this:
self.arrayOfImgObj.append(forTable(imageTempt: image1ToDisplay, users: snapshot.key))
Thanks in advance!
Your problem is that you have two arrays:
arrayOfUIDs which has one entry for each user
arrayOfImgObj which has 0-n entries for each user (So, it is likely that the number of objects in this array will be greater than the number of objects in arrayOfUIDs.
You are basing your row count on the arrayOfImgObj, but then using the row number to index into arrayOfUIDs, which results in an array bounds exception.
It may well be more elegant to have table view section per userid, but if you want to put all of the rows in a single section I would suggest you use a single array of structs as your data model.
Use something like:
struct UserImage {
var userID: String
var image: UIImage
}
var tableData = [UserImage]()
Then as you fetch each image, create a new struct and put it in your array:
func fetchAllUserFristImage() {
Database.database().reference().child("Posts").observe(.childAdded, with: {(snapshot) in
if snapshot.value as? [String: AnyObject] != nil {
let user = snapshot.key
self.databaseRef = Database.database().reference()
let usersPostRef2 = self.databaseRef.child("Posts").child(user)
usersPostRef2.observe(.value, with: {(postXSnapshots) in
if let postDictionary2 = postXSnapshots.value as? [String:AnyObject] {
for (p) in postDictionary2 {
if let posts = p.value as? [String:AnyObject] {
//to get back to where i was delete the below for i
for (i) in posts {
if let imageUrlString = i.value as? [String:AnyObject], let postUrl = imageUrlString.["image1"] as? String {
self.feedArray.append(Post(fetchedImageURL: postUrl))
if let imageUrl = URL(string: postUrl), let imageDataL = try? Data(contentsOf: imageUrl), let image = UIImage(data: imageDataL) {
self.tableData.append(UserImage(userID: user, image: image))
} else {print("this user had no posts, was nil")}
}
}
}
}
}
})
//below shud stay same
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
Now, your cellForRow can be:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.imageView?.image = tableData[indexPath.row].image
cell.textLabel?.text = tableData[indexPath.row].userID
return cell
}

swift table view cells does not let me use indexPath.row

i am trying to populate data into cells inside of a table view. I created a chosenPlanData var which is initialized to an object with the data inside of it.. The object has properties such as "name" and "event location". An issue occurs when inside of 'cellForRowAt'. It does not let me add [indexPath.row] to the cell i am creating, which in turn does not populate the cells correctly.
For instance - i removed indexPath.row from the first cell.nameLbl.text call - and in turn every single name label in the table view was the same. here is piece of the code
var chosenPlanData = ChosenPlan()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "individualPlansCell") as? PlanitsHomeViewCell else { return UITableViewCell() }
cell.nameLbl.text = chosenPlanData.nameOfEvent[indexPath.row] // error Cannot assign value of type 'Character' to type 'String?'
cell.dateAndTimeLbl.text = chosenPlanData.eventStartsAt[indexPath.row] as? String // error 'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion
cell.nameLbl.text = chosenPlanData.nameOfEvent // This works - but every single cell has the same nameLbl though
return cell
}
// Here is the call back where i initialize the value for chosenPlanData
let EventbriteTVC = segue.destination as! EventbriteTableView
EventbriteTVC.callbackChosePlan = { result in
DispatchQueue.main.async {
self.individualPlanitsTableView.reloadData()
}
self.chosenPlanData = result
}
import Foundation
import UIKit
class ChosenPlan {
var nameOfEvent : String = ""
var eventStartsAt : String = ""
var eventLocationIs : String = ""
var eventURL : String = ""
var imageForPlan : String?
convenience init( eventName: String, eventTime: String, eventLocation: String, eventImage: String){
self.init()
self.nameOfEvent = eventName
self.eventStartsAt = eventTime
self.eventLocationIs = eventLocation
//self.eventURL = eventLink
self.imageForPlan = eventImage
//eventLink: String,
}
}
Your chosenPlanData variable is a single instance of ChosenPlan - You cannot subscript a single instance.
It needs to be an array of ChosenPlan:
var chosenPlanData = [ChosenPlan]()
Then you can index into this array:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "individualPlansCell") as? PlanitsHomeViewCell else { return UITableViewCell() }
cell.nameLbl.text = chosenPlanData[indexPath.row].nameOfEvent
cell.dateAndTimeLbl.text = chosenPlanData[indexPath.row].eventStartsAt
extending my comment
var chosenPlanData = ChosenPlan()
chosenPlanData is object of ChosenPlan
Now in cellForRow you writing chosenPlanData.nameOfEvent[indexPath.row] but nameOfEvent is String as per your ChosenPlan that you mentioned in question.
For more info,
chosenPlanData.nameOfEvent[indexPath.row] this line represents you using the n th (indexPath.row) object of nameOfEvent which is object of chosenPlanData
Hope now will be more cleared.
Solution
var chosenPlanData = [ChosenPlan]() <- create array
In cellForRow chosenPlanData[indexPath.row].nameOfEvent that means you'r using nameOfEvent of nth object of chosenPlanData.
Type handling is very important in Swift. Try this.
cell.nameLbl.text = String(chosenPlanData.nameOfEvent[indexPath.row])
let index = chosenPlanData.eventStartsAt.index(chosenPlanData.eventStartsAt.startIndex, offsetBy: 3)
cell.dateAndTimeLbl.text = String(chosenPlanData.eventStartsAt[index])
if
var chosenPlanData = [ChosenPlan]()
Try this:-
cell.nameLbl.text = "\(chosenPlanData[indexPath.row].nameOfEvent)"
or
cell.nameLbl.text = "\(chosenPlanData[indexPath.row].nameOfEvent ?? "")"

How to Unwrap a Modal class object in Swift 3

Model Class:
class CountryModel: NSObject {
var name:NSString!
var countryId:NSString!
init(name: NSString, countryId: NSString) {
self.name = name
self.countryId = countryId
}
}
ViewController:
var nameArr = [CountryModel]() // Model Class object
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let arr = nameArr[indexPath.row] // How to do if let here
let str:String? = "\(arr.countryId) \(arr.name)"
if let sstr = str{
cell.textLabel?.text = sstr
}
return cell
}
How should one unwrap this because output is an optional, if I try to unwrap nameArr[indexPath.row] gives an error initializer for conditional binding must have optional type, not "country modal"
It works fine I am not concatenating arr.countryId with arr.name
This works fine
var nameArr = [CountryModal]()
nameArr.append(CountryModal.init(name: "harshal", countryId: "india"))
nameArr.append(CountryModal.init(name: "james", countryId: "australia"))
let arr = nameArr[0]
let str:String? = "\(arr.countryId!) \(arr.name!)"
if let sstr = str{
print(sstr)
}
let arr2 = nameArr[1]
let str2:String? = "\(arr2.countryId!) \(arr2.name!)"
if let sstr2 = str2{
print(sstr2)
}
You can try this library https://github.com/T-Pham/NoOptionalInterpolation. It does exactly that
import NoOptionalInterpolation
let n: Int? = 1
let t: String? = nil
let s: String? = "string1"
let o: String?? = "string2"
let i = "\(n) \(t) \(s) \(o)"
print(i) // 1 string1 string2
NoOptionalInterpolation gets rid of "Optional(...)" and "nil" in Swift's string interpolation. This library ensures that the text you set never ever includes that annoying additional "Optional(...)". You can also revert to the default behaviour when needed.
Also, please note that this does not affect the print function. Hence, print(o) (as opposed to print("(o)"), o as in the example above) would still print out Optional(Optional("string2"))
Avoid forced unwrapping as much as possible. i.e. using '!'. Whenever you see !, try and think of it as code smell.
For json parsing you would not know for sure if the values for countryId and name exists or not. So, it makes sense to keep them as optionals and gracefully try to unwrap them later.
class CountryModel {
var name: String?
var countryId: String?
init(name: String?, countryId: String?) {
self.name = name
self.countryId = countryId
}
}
var nameArr = [CountryModel]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
fatalError("cell should always be initialised")
}
var str: String?
let arr = nameArr[indexPath.row] // How to do if let here
if let countryId = arr.countryId, let name = arr.name {
str = "\(countryId) \(name)"
}
if let unwrappedString = str {
cell.textLabel?.text = unwrappedString
}
return cell
}
Bonus:
You could simplify your json parsing a lot using the Object Mapper library. It simplifies a lot of your parsing logic.

how to pass dictionary with key to table view

import UIKit
import Firebase
class PendingVC: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var myTableView: UITableView!
let ref = firebasehelper.firebaseURL()
var data = [[:]]
//MARK: vars
var address:AnyObject!
var postTitle:AnyObject!
override func viewDidLoad() {
super.viewDidLoad()
myTableView.dataSource = self
myTableView.delegate = self
//The example below work great I get the layout the way it should look, but i want to generate the dictionary from the firebase function below.
/*
self.data = [
[
"firstname": "sallie",
"lastname": "ammy"
],
[
"firstname": "jamie",
"lastname": "brown"
]
]
*/
It should look something like this and i want to past the data to the table. Im not sure if i should be looping. the way it is below bring the following error "fatal error: unexpectedly found nil while unwrapping an Optional value" the variables are not nil when i print them i get data back.
ref.childByAppendingPath("backend/posts").queryOrderedByChild("requestFrom").queryEqualToValue(ref.authData.uid).observeEventType(.ChildAdded, withBlock: {snapshot in
var firstname = snapshot.value["firstname"] as! String
var lastname = snapshot.value["lastname"] as! String
self.data = [
[
"firstname": firstname,
"lastname": lastname
]
]
print(self.data)
})
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! statusPrototypeCell
let object = data[indexPath.row]
cell.firstname.text = object["firstname"] as! String
cell.lastname.text = object["lastname"] as! String
return cell
}
override func viewWillAppear(animated: Bool) {
navigationController?.navigationBarHidden = false
navigationController?.navigationBar.barTintColor = UIColor(red:0.4, green:0.76, blue:0.93, alpha:1.0)
navigationController?.navigationBar.translucent = false
self.title = "Signup"
self.navigationController?.navigationBar.tintColor = UIColor.whiteColor()
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
}
}
While you can use a dictionary as a dataSource, it's unordered which means the items in your tableView will also be unordered. Using an array is a better solution. In fact, an array of dictionaries make a nice ordered data source.
Also, to clarify, you don't pass a dictionary or data to a tableView per your question. The tableView gathers it's data from a datasource via it's delegate methods
Assume the following Firebase data structure
"users" : {
"uid_0" : {
"first_name" : "Bill",
"last_name" : "Nye"
},
"uid_1" : {
"first_name" : "Leroy",
"last_name" : "Jenkins"
},
"uid_2" : {
"first_name" : "Peter",
"last_name" : "Sellers"
}
}
and to populate an array of dictionaries:
var usersArray: [Dictionary<String, String>] = []
let usersRef = self.myRootRef.childByAppendingPath("users")
usersRef.observeEventType(.ChildAdded, withBlock: { snapshot in
var userDict = [String:String]()
userDict["key"] = snapshot.key
userDict["firstName"] = snapshot.value["first_name"] as? String
userDict["lastName"] = snapshot.value["last_name"] as? String
self.usersArray.append(userDict)
})
to access the data, use the keys you created above.
For example: to print the users in the array from a button
for userDict in self.usersArray {
let key = userDict["key"]
let fName = userDict["firstName"]
let lName = userDict["lastName"]
print("\(key!) \(fName!) \(lName!)")
}
Once you have an understanding of that, you can then use the usersArray to populate the tableView.
let userDict = usersArray[indexPath.row]
cell.firstname.text = userDict["firstName"] as! String
cell.lastname.text = userDict["lastName"] as! String
The tricky bit is loading the array with all of the data needed, then reload the tableView to display it. If you have a small set of data, .Value will work for that. A larger set of data requires another technique, see This Answer
You are using Dictionary so it doesn't returning count value so better to use array like [] instead of [:]
One more thing You have forgot the following statement to include in ViewDidLoad method
myTableView.delegate = self

Autocomplete Search gets slow and takes memory while it fetches the result from database

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.

Resources