Swift: app crashes after deleting a column in Parse - ios

In Parse I accidentally deleted a column called "likes" that counts the number of a likes a user receives for their blog post. I created the column again with the same name but now when I run my app it crashes and I receive this message "unexpectedly found nil while unwrapping an Optional value". It points to my code where its suppose to receive the "likes" in my cellForRowAtIndexPath. I pasted my code below. Is there any way I could fix this issue and stop it from crashing?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath?) -> PFTableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("BCell", forIndexPath: indexPath!) as! BlogCell
if let object : PFObject = self.blogPosts.objectAtIndex(indexPath!.row) as? PFObject {
cell.author.text = object["blogger"] as? String
cell.post.text = object["blogPost"] as? String
let dateUpdated = object.createdAt! as NSDate
let dateFormat = NSDateFormatter()
dateFormat.dateFormat = "h:mm a"
cell.timer.text = NSString(format: "%#", dateFormat.stringFromDate(dateUpdated)) as String
let like = object[("likes")] as! Int
cell.likeTracker.text = "\(like)"
}
return cell
}

I would inspect what's going on with the object if I were you. You clearly aren't getting data that you expected to be there. As a stopgap, you can change let like = object["likes"] as! Int to
if let like = object["likes"] as? Int {
cell.likeTracker.text = "\(like)"
}
If you do that, you will also want to implement the prepareForReuse method in BlogCell to set that label's text to nil or else you might have some weird cell reuse bugs.

Where you delete a column from uitableview , you need to delete data from data source, and update the delete index or reload the whole table .
Look for if you are missing that step

Related

How to update UITableviewcell label in other method in swift3?

I am trying to update a UITableviewcell's label in other method. I also saw many post on stackoverflow, non was work for me. i am using Swift 3 and I tried this one :
let indexPath = IndexPath.init(row: 0, section: 0)
let cell : CategoryRow = tableView.cellForRow(at: indexPath) as! CategoryRow
and i am getting this error :
fatal error: unexpectedly found nil while unwrapping an Optional value
i am using this in other method where i update tableviewcell label text after 24hr.
code is here
var myQuoteArray = ["“If you have time to breathe you have time to meditate. You breathe when you walk. You breathe when you stand. You breathe when you lie down. – Ajahn Amaro”","We are what we repeatedly do. Excellence, therefore, is not an act but a habit. – Aristotle","The best way out is always through. – Robert Frost","“If you have time to breathe you have time to meditate. You breathe when you walk. You breathe when you stand. You breathe when you lie down. – Ajahn Amaro”","We are what we repeatedly do. Excellence, therefore, is not an act but a habit. – Aristotle","The best way out is always through. – Robert Frost"
]
func checkLastRetrieval(){
// let indexPath = IndexPath.init(row: 0, section: 0)
// let cell : CategoryRow = tableView.cellForRow(at: indexPath) as? CategoryRow
let indexPath = IndexPath.init(row: 0, section: 0)
guard let cell = tableView.cellForRow(at: indexPath) as? CategoryRow else { return }
print("Getting Error line 320")// This is not printing on console
let userDefaults = UserDefaults.standard
if let lastRetrieval = userDefaults.dictionary(forKey: "lastRetrieval") {
if let lastDate = lastRetrieval["date"] as? NSDate {
if let index = lastRetrieval["index"] as? Int {
if abs(lastDate.timeIntervalSinceNow) > 86400 { // seconds in 24 hours
// Time to change the label
var nextIndex = index + 1
// Check to see if next incremented index is out of bounds
if self.myQuoteArray.count <= nextIndex {
// Move index back to zero? Behavior up to you...
nextIndex = 0
}
cell?.quotationLabel.text = self.myQuoteArray[nextIndex]
let lastRetrieval : [NSObject : AnyObject] = [
"date" as NSObject : NSDate(),
"index" as NSObject : nextIndex as AnyObject
]
userDefaults.set(lastRetrieval, forKey: "lastRetrieval")
userDefaults.synchronize()
}
// Do nothing, not enough time has elapsed to change labels
}
}
} else {
// No dictionary found, show first quote
cell?.quotationLabel.text = self.myQuoteArray.first!
print("line 357")
// Make new dictionary and save to NSUserDefaults
let lastRetrieval : [NSObject : AnyObject] = [
"date" as NSObject : NSDate(),
"index" as NSObject : 0 as AnyObject
]
userDefaults.set(lastRetrieval, forKey: "lastRetrieval")
userDefaults.synchronize()
}
}
and Calling this method in ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
checkLastRetrieval()
}
what should i do?
Thanks in Advance
The function cellForRow(at:) returns nil if the requested cell is not currently onscreen. Since you are calling your function in viewDidLoad there will be no cells on screen. This results in the crash because you have force downcast the result of cellForRow(at:).
You should never use a force unwrap or a force downcast unless you are absolutely
certain the result cannot be nil and/or the only possible action if it is nil is for your app to crash.
Generally you should not update cells directly. It is better to update the table's data model and then call reloadRows(at:,with:) and let your data source cellForRow(at:) function handle updating the cell.
If you are going to update the cell directly then make sure you use a conditional downcast with if let or guard let to avoid a crash if the cell isn't visible. In this case you still need to update your data model so that the cell data is correct when the cell does come on screen.
You should use guard let statement:
let indexPath = IndexPath(row: 0, section: 0)
guard let cell = tableView.cellForRow(at: indexPath) as? CategoryRow else { return }
If your cell type is not CategoryRow, you will return from the method. Try to find out why is your cell type not a CategoryRow.

Array values not showing up in custom table cell

I am making a simple weather app and my table view is supposed to show the weeks forecast by showing the day on the left side and the high temp on the right side. My cellForRowAtIndexpath is below. It correctly displays the dayarray in my custom daylabel but the maxarray which contains max temperatures doesn't show up. When I print out the max array values it looks correct and all the numbers are listed there but still does not show up. If I just hard code a value in that text label like "Test" then that will show up but the array doesn't.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! DailyWeatherTableViewCell
cell.daylabel.text = dayarray[indexPath.row] as? String
cell.maxlabel.text = maxarray[indexPath.row] as? String
return cell
UPDATE: My array is set up like this:
dayarray = [dateString1,dateString2, dateString3, dateString4, dateString5, dateString6, dateString7]
maxarray = [max1int, max2int, max3int, max4int, max5int, max6int, max7int]
Its kind of complicated where these values come from but if I print out maxarray, it looks like this:
100
100
92
90
90
89
89
I think the way you cast the Int to String cause the value to nil. Try change to below instead:
cell.maxlabel.text = String(maxarray[indexPath.row])
The different is that String() is create an object of string from the Int value. while the as? operator normally used when you have different type of objects in your array and you want to cast it to specific type. for example
let arr = [1, 21, 3, 5, "Hello world", 1, 2, "food"]
for element in arr {
if let stringValue = element as? String {
print(stringValue)
}
else if let intValue = element as? Int {
print (intValue)
}
}

How to check a single time if UserDefaults is empty

My app counts the days between a date and NSDate(). When I released it, a user could only save one date, a title and a background image.
Now I have a UICollectionView with the option to save more than one date, and it will create a cell by appending a date, title and image string to their respective arrays.
The app has been completely changed, so I'm struggling with how to check whether a user has saved a date, and if they have, add that info to the arrays to create a cell.
But, I want this to only been checked once - the first time the app is opened from update or fresh install.
Here is my thinking about it, it doesn't work by the way.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MyCollectionViewCell
if userDefault.objectForKey("day") == nil {
} else {
// Add the first date created from previous version
let day = userDefault.objectForKey("day") as? String
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd MMMM yyyy hh:mm a"
let date = dateFormatter.dateFromString(day!)!
let date1 = dateFormatter.stringFromDate(date)
addDateToArray(date1)
// Add the since text
let text = userDefault.objectForKey("sinceText") as? String
addSinceLabelToArray(text!)
//Add the image background
let image = userDefault.objectForKey("khoury") as! String
addThemeToImagesArray(image)
}
What happens with the code above is it returns nil. I am expecting it to create the first cell, so that the user can see the date they have saved.
You can use another boolean value in NSUserDefaults to detect the first run:
let defaults = NSUserDefaults.standardUserDefaults()
if (!defaults.boolForKey("migrated")) {
defaults.setBool(true, forKey: "migrated")
// ... anything else
}
Create an array within your view controller.
var dates = [NSDate]()
Then in viewDidLoad:
if let retrieved = userDefault.objectForKey("day") {
dates = retrieved as! [NSDate]
}
Then reload your collection view

Swift and Parse - Message app, cannot display username within Xcode 6.3

I have some issue to display the 'author's username of each posts in my TableViewController.
It actually display the current user's username for all display posts, how to display each poster's username ?
I'm using Xcode 6.3 and Parse.com API.
The timeLabel is displayed correctly, but the userLabel display the current user who is logged in instead of the author of the post.
If I logged out and login with a different username all the userLabel change to the new user. The debug console display Optional("theNameOfTheCurrentUser") as many times as there are posts displayed.
Parse host 2 DB one for users (User) and one for posts (Poemes), there is a pointer in Poemes table to the specific user.
I update to Xcode 6.3 lately and had an error on var findLover:PFQuery = PFUser.query()
Value of optional type 'PFQuery?' not unwrapped
I add the exclamation mark (!) at the end of this line, which remove the error, is this causing the issue ?
I read Parse documentation and follow some exemples but looks like I'm a bit lost here, any help and suggestions will be highly appreciated, thanks.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:DisplayTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! DisplayTableViewCell
let poeme:PFObject = self.timelineData.objectAtIndex(indexPath.row) as! PFObject
cell.poemeLabel.text = poeme.objectForKey("content") as? String
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd HH:mm"
cell.timeLabel.text = dateFormatter.stringFromDate(poeme.createdAt!)
var findUser:PFQuery = PFUser.query()!
findUser.findObjectsInBackgroundWithBlock {
(objects, error)->Void in
if var objects = objects {
let author:PFUser = (objects as NSArray).lastObject as! PFUser
cell.userLabel.text = author.username
println(author.username)
})
}
return cell
}
The function findUser.findObjectsInBackgroundWithBlock happens in the background, while the main thread still running, so by the time you get the response from parse with the values you need the cell you are trying to return in the function is long gone.
The easiest way to fix it is to fetch all the data you need before hand and safe it in a array and use this array to populate the cell.
Finally get it work for Xcode 6.3.2 changes, here is the result :
unwrap and optional seams to be my main problem :
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
let cell:DisplayTableViewCell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as! DisplayTableViewCell
let poeme:PFObject = self.timelineData.objectAtIndex(indexPath!.row) as! PFObject
cell.poemeLabel.text = poeme.objectForKey("content") as! String
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd HH:mm"
cell.timeLabel.text = dateFormatter.stringFromDate(poeme.createdAt!)
var findUser:PFQuery = PFUser.query()!
findUser.whereKey("objectId", equalTo: poeme.objectForKey("user")!.objectId!!)
findLover.findObjectsInBackgroundWithBlock {
(objects, error)->Void in
if var objects = objects {
let author:PFUser = (objects as NSArray).lastObject as! PFUser
cell.userLabel.text = author.username
println(author.username)
})
}
}
return cell
}

Core Data to create TableViewCells Swift

So I am trying to create a TableViewCell with Core Data, but when defining the cells, they all turn in the last input at the Core Data. The app is taking the user textfield input and turning into the table view cell label, and the zipInStr to the TableViewCell detail.
This is the function that add the values to the CoreData:
#IBAction func createFav(sender: AnyObject) {
//Defining variables to save at core data
var newTextInput = textInput.text
var trimmNewTextInput = newTextInput.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
var zipInStr: String = zipCode.text!
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var newFav = NSEntityDescription.insertNewObjectForEntityForName("Cells", inManagedObjectContext: context) as NSManagedObject
newFav.setValue(zipInStr, forKey: "favsDictKey")
newFav.setValue(trimmNewTextInput, forKey: "favsDictValues")
context.save(nil)
println(newFav)
textInput.text = String()
}
}
And this is the function that is creating the TableViewCells:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Cells")
request.returnsObjectsAsFaults = false
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
var results: NSArray = context.executeFetchRequest(request, error: nil)!
println(results)
if (results.count == 0){
println("Error")
} else {
for res in results{
cell.textLabel?.text = res.valueForKey("favsDictValues") as? String
cell.detailTextLabel?.text = res.valueForKey("favsDictKey") as? String
}
}
return cell
}
I am pretty sure the error have something to do with the loop, since when I print the results I can see all the inputs with its respective values
Your setup is not correct. You should employ a NSFetchedResultsController (see Xcode template for easy code to copy) - this is the best way to populate a table view with Core Data.
In cellForRowAtIndexPath you simply retrieve the correct item for the index path. Don't put fetch request into this method, as it is potentially called many times (e.g. while scrolling).
let cell = self.fetchedResultsController.objectAtIndexPath(indexPath) as Cells
BTW, "Cells" seems to be an inappropriate name for an entity. You need a singular that describes the data you are displaying. Perhaps, "Favorite"?
As per my comment, to get your current setup working, make results into a stored property. Add
var results : NSArray = NSArray()
at the beginning of your class definition. Then move the code to populate the array from your cellForRowAtIndexPath to viewDidLoad:
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Cells")
request.returnsObjectsAsFaults = false
results = context.executeFetchRequest(request, error: nil)!
println("\(results)")
Now, the tableView will call cellForRowAtIndexPath multiple times, once for each row that it wants to display. So the code in that method needs to configure it with the data for the relevant row. The indexPath provided as an argument to the call indicates which row (and section, if you break your table view into sections) the tableView is requesting. So your code does not need to loop through all the values in results - it just needs to reference the correct element of the array.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let res = results[indexPath.row] as NSManagedObject
cell.textLabel?.text = res.valueForKey("favsDictValues") as? String
cell.detailTextLabel?.text = res.valueForKey("favsDictKey") as? String
return cell
}
That should get your tableView working (unless I've messed up some optionals or casts - if so, sorry, I'm still getting up to speed with Swift).
This approach will work fine provided your data is relatively static (i.e. doesn't change whilst the tableView is being displayed) and provided you don't want to break the table into sections. In either of these situations (and, some would say, in any case), you should use an NSFetchedResultsController. That will automatically keep track of how many (and which) items appear in each section, etc, and can also be configured to automatically update the tableView if new items are added to the data whilst it is being displayed.

Resources