strange bug with tableView reload data - ios

I have a tableView with messages.
Messages are store into parse.com.
I download asynchronously, put the message in a struct message array
import UIKit
var messMgr : messageObjet = messageObjet()
var date : NSDate = NSDate()
struct message {
var commentText = ""
var commentDate = ""
}
class messageObjet: NSObject {
var messageData = [message]()
func addMessage(comment : String, date : String) {
//messageData.append(message(commentText: comment, commentDate: date))
var mess = message(commentText: comment, commentDate: date)
messageData.insert(mess, atIndex: 0)
}
}
and populate my tableView
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CommentTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as CommentTableViewCell
cell.tag = indexPath.row
// Configure the cell...
// check if it is in the cache
println("the messageCache in cellForRow in commentTableViewController is \(self.commentCache.objectForKey(indexPath.row))")
if let messageCached: AnyObject = self.commentCache.objectForKey(indexPath.row){
cell.messageLabel.text = messageCached as? String
}
if let dateCached: AnyObject = self.dateCache.objectForKey(indexPath.row){
cell.dateLabel.text = dateCached as? String
}
else if messMgr.messageData.count != 0 {
if cell.tag == indexPath.row {
cell.messageLabel.text = messMgr.messageData[indexPath.row].commentText
self.commentCache.setObject(cell.messageLabel.text!, forKey: indexPath.row)
cell.dateLabel.text = messMgr.messageData[indexPath.row].commentDate
self.dateCache.setObject(cell.dateLabel.text!, forKey: indexPath.row)
}
}
return cell
}
I have modal viewController to add a new message.
In order to display immediately the message after dismissing the modal VC
i did this
#IBAction func sendComment(sender: AnyObject) {
let uuid = userDefaults.stringForKey("ApplicationUniqueIdentifier")
var comment = PFObject(className:"Comment")
comment.setObject(textToSend.text, forKey: "CommentText")
comment.setObject(post, forKey: "Post")
comment.setObject(uuid, forKey: "from")
comment.saveEventually()
let date = NSDate()
newMessage.commentText = textToSend.text as String
newMessage.commentDate = date.relativeTimeToString() as String
messMgr.messageData.insert(newMessage, atIndex: 0)
// messMgr.addMessage(textToSend.text as String, date: date.relativeTimeToString() as String)
NSNotificationCenter.defaultCenter().postNotificationName("reloadMessageTableView", object: nil)
println(messMgr.messageData)
self.dismissViewControllerAnimated(true, completion: nil)
}
The problem is when i come back to my tableView the message added to the index 0 is always displayed as the previous message and when i print my message array the index 0 message is the good one..
Any idea ?

Ok the problem was that i store the message in a NSCache with the indexPath as a key...
Removed this and everyThings work good.

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
}

How to retrieve UserDefaults because cells are empty

I made a struct dictionary to get the user title and URL, and then I store them on the phone but when I come to retrieve the data in cellForRow method the cell label is empty, what should appear is the title.(tableView starts off empty until user starts to populate it with the AddArticle action)
So my question is if I'm doing it right because the cell label just turns out nil?
Struct Dictionary:
struct AddMagazine {
let rssUrl: String
let title: String
init(dict: [String : String]){
title = dict["title"] ?? ""
rssUrl = dict["rssUrl"] ?? ""
}
}
var userMagazineTitle = [AddMagazine]()
Getting values from textField:
#IBAction func AddArticle(_ sender: Any) {
animateIn()
tableView.isScrollEnabled = false
}
func addArticleTitle() {
let UserMagazines = AddMagazine.init(dict: ["title": RssTitle.text!, "rssUrl": RssText.text!])
let storedRssUrl = UserMagazines.rssUrl
self.dataString = storedRssUrl
//setting
defaults.set(dataString, forKey: "storedArray")
userMagazineTitle.append(UserMagazines)
tableView.reloadData()
}
Trying to retrieve title here:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyFeedTableViewCell
let headlineName = defaults.object(forKey: "storedArray") as? AddMagazine
cell.myHeadline.text = headlineName?.title
cell.indentationLevel = 3
return cell
}
You’re storing a String object in defaults for “storedArray” but then you typecast it to an AddMagazine when you read it from defaults. Change what you store or read it as a string.
I agree with #Joakim Danielson. You are storing storedRssUrl which is a string into userdefaults and while retrieving you are type casting as AddMagazine hence it will be nil.
self.dataString = storedRssUrl
//setting
defaults.set(dataString, forKey: "storedArray") --> Here you are storing string
let headlineName = defaults.object(forKey: "storedArray") as? AddMagazine --> Here you are fetching as AddMagazine.
//It should be like this
let headlineName = defaults.object(forKey: "storedArray") as? String

Creating an NSCache for the first time with CloudKit Records - close?

I have a project I'm close to completing. My last problem arises when I've downloaded CloudKit records to an array to be displayed in a tableview. Here is my code for the query portion of the controller.
for result in results!
{
let tablerestaurant = Restaurant()
if let name = result.value(forKey: "Name") as! String? {
tablerestaurant.name = name
}
// Do same for image
if let imageAsset = result.object(forKey: "Picture") as! CKAsset? {
if let data = try? Data(contentsOf: imageAsset.fileURL) {
tablerestaurant.image = UIImage(data: data)
}
}
self.tablerestaurantarray.append(tablerestaurant) // tablerestaurant is an array holding string and image instances of class Restaurant
self.restaurantArray.append(result) // restaurantArray holds CKRecords to be segued to the next controller
OperationQueue.main.addOperation( { () -> Void in
self.tableView.reloadData()
self.activity.isHidden = true
})}
and here is my cellForRowAtIndexPath Tablecell function, with the cache portion commented along with the global arrays here
var tablerestaurantarray: [Restaurant] = []
let cache = NSCache<NSString, Restaurant>()
-
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "restaurantcell") as? RestaurantTableCell
/////////this is the cache portion//////////////
var oneRestaurant: Restaurant = tablerestaurantarray[indexPath.row]
if let cachedVersion = cache.Restaurant(forKey: "image")
{
oneRestaurant = cachedVersion
}
else
{
oneRestaurant = Restaurant()
cache.setObject(oneRestaurant, forKey: "image")
}
//////////////////////////
let restaurant: Restaurant = tablerestaurantarray[indexPath.row]
cell?.name?.text = oneRestaurant.name
// cell?.picture?.image = oneRestaurant.image
return cell!
I've attempted to copy this simply cache procedure from here https://www.hackingwithswift.com/example-code/system/how-to-cache-data-using-nscache and transcribe it towards my own use. However, the debugger states that
NSCache<NSString, Restaurant> has no member Restaurant
which it does. So I'm lost as to my problem. Would you have an idea?
Update 2
var tablerestaurantarray: [Restaurant] = []
let cache = NSCache<NSString , Restaurant>()
var oneRestaurant: Restaurant = tablerestaurantarray[indexPath.row]
if let cachedVersion = cache.object(forKey: "image") { } //error is called here
{
oneRestaurant = cachedVersion
}
else
{
oneRestaurant = Restaurant()
cache.setObject(oneRestaurant, forKey: "image")
}

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.

How to display a number/integer in a TableView?

I'm trying to build a simple app in which the user would input a date from a date picker(a birthday or any event), then the code would subtract today's date from the date the user gave the app, to get the difference in days and then display a countdown of the number of days left in a table view, along with the name of the event. I have 2 View Controllers, the main TableViewController and then the AddViewController. I've figured out how to get the difference in days, my table view is set up and all the code is running quite well. I just have a problem in displaying the number of days left in the table view as they are integers. I would love to get some help from more experienced developers as I'm relatively new to iOS programming. So without further due, here's my code:
#IBAction func addButtonTapped(sender: AnyObject) {
var chosen: NSDate = self.datePicker.date
var today: NSDate = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.DayCalendarUnit, fromDate: today, toDate: chosen, options: nil)
let secondsInADay = ((60 * 60) * 24)
let daysLeft = (components.hashValue / secondsInADay)
var userDefaults: NSUserDefaults = NSUserDefaults()
var itemList: NSMutableArray? = userDefaults.objectForKey("itemList") as? NSMutableArray
var dataSet: NSMutableDictionary = NSMutableDictionary()
dataset.setObject(eventTextField.text, forKey: "itemEventName")
dataSet.setObject(datePicker.date, forKey: "itemEventDate")
dataSet.setObject(daysLeft, forKey: "itemDaysLeft")
if ((itemList) != nil) {
var newMutableList: NSMutableArray = NSMutableArray()
for dict: AnyObject in itemList! {
newMutableList.addObject(dict as NSDictionary)
}
userDefaults.removeObjectForKey("itemList")
newMutableList.addObject(dataSet)
userDefaults.setObject(newMutableList, forKey: "itemList")
} else {
userDefaults.removeObjectForKey("itemList")
itemList = NSMutableArray()
itemList!.addObject(dataSet)
userDefaults.setObject(itemList, forKey: "itemList")
}
userDefaults.synchronize()
self.navigationController?.popToRootViewControllerAnimated(true)
}
So above is the code in my addViewController, users input a name from a text field, and a date from a date picker. Below is a part of the code in my tableViewController. Basically I want the Int daysLeft to be displayed in the cell.
var toDoItems: NSMutableArray = NSMutableArray()
override func viewDidAppear(animated: Bool) {
var userDefaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
var itemListFromUserDefaults: NSMutableArray? = userDefaults.objectForKey("itemList") as? NSMutableArray
if ((itemListFromUserDefaults) != nil) {
toDoItems = itemListFromUserDefaults!
}
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
var toDoItem: NSDictionary = toDoItems.objectAtIndex(indexPath.row) as NSDictionary
cell.textLabel?.text = toDoItem.objectForKey("itemDaysLeft") as? String
return cell
}
Put a break point on this line: var toDoItem: NSDictionary = toDoItems.objectAtIndex(indexPath.row) as NSDictionary to see what toDoItemContains, just to verify that it is actually what you expect it to be. But I think the below code may help.
Instead of:
cell.textLabel?.text = toDoItem.objectForKey("itemDaysLeft") as? String
try:
cell.textLabel?.text = NSString(format:"%#", toDoItem.objectForKey("itemDaysLeft")) as String

Resources