I am writing a program where the users can send 'tweets'. In each table cell there is going to be the 'tweet' text, a label showing the username of the user who posted it, and another label for the timestamp.
The code to show the 'tweet' itself is working fine, so I left the code for that out. However the code below is where I am trying to get the username and timestamp but when I run the program it does not work. There is no compiler error and the app does not crash, it just all of a sudden returns all empty cells. Not even the 'tweet' text appears (which was working perfectly before I tried to add the username and timestamp).
There is no error message shown by the debugger, although it does say that the break point is on line 153 (near the bottom of my code where I have left a comment)
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
let cell:TextTableViewCell = tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath!) as TextTableViewCell
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
cell.timestampLabel.text = dateFormatter.stringFromDate(post.createdAt)
var findPoster:PFQuery = PFUser.query()
findPoster.whereKey("objectId", equalTo: post.objectForKey("posted_by").objectId)
findPoster.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in
if error == nil{ //*******Breakpoint here********
let user:PFUser = (objects as NSArray).lastObject as PFUser
cell.usernameLabel.text = user.username
}
}
// Configure the cell...
return cell
}
Any ideas what is wrong?
UPDATE:
This code was working with Xcode beta 6, but now it is not, which of course means it is something new in the most recent version in the language.
I figured out why nothing was showing. Every outlet in my program was unconnected... Not sure why though as they were connected and everything was working when I was using beta 6. I definitely didn't manually go and un connect every outlet so I'm not sure why this happened? Maybe it is a bug in the latest version of Xcode? I've had nothing but problems with every upgrade. Hopefully this can help anyone who is in a similar situation.
Related
I am a beginner developer, who tries his best. Currently I'm working on a complicated application, and everything works fine: expect one thing. 1 out of 100 times some basic thing crashes my app.
(This application is a Social posting app)
1. When I open a post to view it's comments, everything works fine. 99% of the time. But last time it crashed out of nowhere. I don't really understand.
2. The other time I opened the app after 1 hour no usage, and suddenly the app crashed.
Could someone help me out? Am I right with the following guesses?
First case:
Pastebin Link
I think this is caused by reloadSections:withRowAnimation: . Am I correct?
Second case:
Pastebin Link
And this is because of performBatchUpdates I call on a collection view. Right?
But I don't really understand where could be the problem's root.
Here's my code, it looks completely harmless (for the second example):
It's a collection view inside a tableCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0 :
let cell = tableView.dequeueReusableCell(withIdentifier: "StoriesCell", for: indexPath) as! StoriesCell
// Could use cell.storiesCollectionView.reloadData() too, but it flickers then
UIView.animate(withDuration: 0, animations: {
cell.storiesCollectionView.performBatchUpdates({
cell.storiesCollectionView.reloadSections(IndexSet(0...0))
})
})
cell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
cell.collectionViewOffset = storedStoriesCollectionViewOffset[indexPath.row] ?? 0
return cell
case 2 :
// ...
On the first View Controller I have a tableview that lists various sites like google, stack overflow... For each service added an image will load based on the first letter of that site. So an image of a T will load for Twitter.
If the user wants he/she can tap that cell and go to a 2nd VC and add the URL. When the user comes back to the first VC that site will try to pull the favicon instead of the letter image... this works, but not gracefully.
What I initially wanted was for each image to show up as soon as it loaded, obviously not all at once and not so it would disrupt the user interaction with the app.
What is happening now is that they show up a few at a time (which is ok) but not in the right place, initially. So say I have amazon, google, Microsoft, Facebook, and apple...the favicon would actually be out of order so Microsoft might have googles logo and after several seconds it might shift to Facebooks and then depending on how many there are it might shift again until it is all in the right matching place…This also happens if i scroll 'below the fold' and after several moments will right itself (the cell title remains in order however)
So I obviously have something in my code wrong, and would love, at the minimum, get it so it puts Facebook right the first time and then google, etc etc
OR another option could be all of the images START out as the letter image and then the code tries to replace it with the favicon...and get it right on the first try..
Any help would be great
Here is my Table View cellForRowAt code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "serviceCell", for: indexPath as IndexPath) as! ServiceTableViewCell
let row = indexPath.row
cell.serviceNameLabel.text = serviceArray[row].serviceName
DispatchQueue.global().async {
let myURLString: String = "http://www.google.com/s2/favicons?domain=\(self.serviceArray[row].serviceUrl)"
if let myURL = URL(string: myURLString), let myData = try? Data(contentsOf: myURL), let image = UIImage(data: myData) {
cell.serviceLogoImage.image = image
} else {
cell.serviceLogoImage.image = UIImage.init(named: "\(self.getLetterOrNumberAndChooseImage(text: self.serviceArray[row].serviceName))")
}
}
return cell
}
}
cell.serviceLogoImage.image =
Must be called from the Main Thread, since you are modifying User Interface.
Apple Documentation
Note For the most part, use UIKit classes only from your app’s main
thread. This is particularly true for classes derived from UIResponder
or that involve manipulating your app’s user interface in any way.
Check this thread how to run the different threads from background and to the main.
I have a pretty complicated table view setup and I resolved to use a block structure for creating and selecting the cells to simplify the future development and changes.
The structure I'm using looks like this:
var dataSource: [(
cells:[ (type: DetailSection, createCell: ((indexPath: NSIndexPath) -> UITableViewCell), selectCell: ((indexPath: NSIndexPath) -> ())?, value: Value?)],
sectionHeader: (Int -> UITableViewHeaderFooterView)?,
sectionFooter: (Int -> UITableViewHeaderFooterView)?
)] = []
I can then set up the table in a setup function and make my delegate methods fairly simple
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = dataSource[indexPath.section].cells[indexPath.row].createCell(indexPath:indexPath)
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource[section].cells.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return dataSource.count
}
I have made a similar setup before in another TVC
var otherVCDataSource: [[ (type: DetailSection, createCell: ((indexPath: NSIndexPath) -> UITableViewCell), selectCell: ((indexPath: NSIndexPath) -> ())?)]] = []
This solution has worked great.
The current dataSource with the sectionHead and footer however gives me a EXC_BAD_ACCESS every time I try to access the indexPath in one of the createCell blocks.
createCell: {
(indexPath) in
let cell:CompactExerciseCell = self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath) as! CompactExerciseCell
cell.nameLabel.text = "\(indexPath.row)"
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
The app always crashes on
self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath)
What am I missing here? Why can't I access the indexPath in the new structure when it works fine in the old structure? What is different in the memory management between this tuple and the array?
UPDATE:
So I had a deadline to keep and I finally had to give up and rework the data structure.
My first attempt was to instead of sending the indexPath as a parameter send the row and section and rebuild an indexPath inside the block. This worked for everything inside the data structure but if I pushed another view controller on a cell click I got another extremely weird crash (some malloc error, which is strange as I use ARC) when dequeuing cells in the next VC.
I tried to dig around in this crash as well but there was no more time to spend on this so I had to move on to another solution.
Instead of this tuple-array [([],,)] I made two arrays; one for the cells and one for the headers and footers. This structure removed the problem of the indexPath crash but I still had the issue in the next VC that didn't stop crashing when dequeueing the cells.
The final solution, or workaround, was to access the cell creator and selector "safely" with this extension:
extension Array {
subscript (safe index: Int) -> Element? {
return indices ~= index ? self[index] : nil
}
}
basically the return statement in the tableView delegate functions then looks like this:
return dataSource[safe:indexPath.section]?[safe:indexPath.row]?.createCell?(indexPath: indexPath)
instead of
return dataSource[indexPath.section][indexPath.row].createCell?(indexPath: indexPath)
I can't see how it makes any difference to the next VC as the cell shouldn't even exist if there was an issue with executing nil or looking for non existing indexes in the data structure but this still solved the problem I was having with the dequeueing of cells in the next VC.
I still have no clue why the change of data structure and the safe extension for getting values from an array helps and if someone has any idea I would be happy to hear it but I can not at this time experiment more with the solution. My guess is that the safe access of the values reallocated the values somehow and stopped them from being released. Maybe the tuple kept the compiler from understanding that the values should be kept in memory or maybe I just have a ghost in my code somewhere. I hope one day I can go back and dig through it in more detail...
This is NOT an answer to the question but rather a workaround if someone ends up in this hole and has to get out:
First use this extension for array:
extension Array {
subscript (safe index: Int) -> Element? {
return indices ~= index ? self[index] : nil
}
}
And then in the table view delegate functions use the extension like this
let cell = dataSource[safe:indexPath.section]?[safe:indexPath.row]?.createCell?(indexPath: indexPath)
If this does not work remove the tuple from the data structure and you should have a working solution.
I wish you better luck with this issue than I had.
you have to register your tableview cell for particular cell idntifier in viewdidload.
eg.tableview.registerNib(UINib(nibName: "cell_nib_name", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: "cell_identifier");
for deque cell
let cell:CompactExerciseCell = self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath) as! CompactExerciseCell
like this.
Ok so I've been trying to make a like button for an app I'm working on and I'm using parse as a backend. So far I can update the like button in parse, however, I can only reload the entire tableview not just the single cell. Reloading the entire tableview sometimes causes the tableview to move up or down I believe because of different sized cells.
#IBAction func topButton(sender: UIButton) {
let uuid = UIDevice.currentDevice().identifierForVendor.UUIDString
let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
let object = objectAtIndexPath(hitIndex)
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: hitIndex!) as TableViewCell
//var indexOfCell:NSIndexPath
//indexOfCell = hitIndex!
if object.valueForKey("likedBy") != nil {
var UUIDarray = object.valueForKey("likedBy")? as NSArray
var uuidArray:[String] = UUIDarray as [String]
if !isStringPresentInArray(uuidArray, str: uuid) {
object.addObject(uuid, forKey: "likedBy")
object.incrementKey("count")
object.saveInBackground()
//self.tableView.reloadData()
}
}
}
I've already tried the cellForRow with the proper indexPath and it always returns that PFTableViewCell does not contain the cellForRowAtIndexPath method. I read on a forum that someone was able to create a custom method to reload the cell and Parse's documentation uses PAPCache to do the like button. Lucky for me the documentation for the like button on Parse's page is in Obj-C and I've coded my app in Swift. Here's the link to the page if you want to see: https://www.parse.com/tutorials/anypic#like. Section 6.1.
TD;LR I'm unsure how to update the cell so the entire tableview does not get reloaded without the cellForRowAtIndexPath method. Maybe Custom method? Maybe PAPCache method?
SOLVED
if object.valueForKey("likedBy") != nil {
var UUIDarray = object.valueForKey("likedBy")? as NSArray
var uuidArray:[String] = UUIDarray as [String]
if !isStringPresentInArray(uuidArray, str: uuid) {
object.addObject(uuid, forKey: "likedBy")
object.incrementKey("count")
object.saveInBackground()
let count = object.valueForKey("count") as Int?
//cell.count.text = "\(count)"
self.tableView.reloadRowsAtIndexPaths([hitIndex!], withRowAnimation: UITableViewRowAnimation.None)
This properly reloads only the cell, however, now my entire tableview always scrolls to the top which is not ideal.
I don't understand what are you trying to with the UUID; hitpoint ;hitIndex, and I don't understand what you want your app to do and I don't have and experience with parse ? I would comment on your question,but I need 50 rep to do this.Sorry that this isn't an answer,but a question .And if you are referring about cellForRowAtIndexPath,you need a UITableView not a cell.
I'm a beginner, clearly out of my league and I haven't been able to find an answer online.
I have a UITableViewController with a UITableViewshowing custom objects stored in one array. I don't show all the object of the array in one single section of said TableView: the TableView has multiple sections, each containing a filtered portion of my objects array (I filter the custom objects array checking that the object category property is equal to a category that I specified in a categories array).
This filtering and showing the single array in different sections is working fine (I understand that maybe it's not elegant, as I said I'm a beginner in coding and I absolutely needed to work with one single array, without creating other arrays corresponding to the filtered results), but to better understand my issue I think it's better that I show what I did, so here's the TableView part of my code:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return myCategoriesArray.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
for (var i = 0; i <= section; i++){
if section == i {
for eachCategory in myCategoriesArray {
return myObjectsArray!.filter() { $0.objectCategoryProperty == myCategoriesArray[i] }.count
}
}
}
// ...
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("objectCell", forIndexPath: indexPath) as UITableViewCell
for (var i = 0; i <= indexPath.section; i++){
if indexPath.section == i {
for eachCategory in myCategoriesArray {
cell.textLabel?.text = myObjectsArray!.filter() { $0.objectCategoryProperty == myCategoriesArray[i] }[indexPath.row].nameProperty
return cell
}
}
}
// ...
}
This works in the sense that I have the UITableViewController showing all my objects, but filtered in separated sections by category.
My issue is with the segue when I select a cell and show a detail view.
Here's my prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var nextVC = segue.destinationViewController as MyNextViewController
if let indexPath = tableView.indexPathForSelectedRow() {
let selected = myObjectsArray![indexPath.row]
nextVC.passedObject = selected
}
}
}
I'm sure that many of you already see my issue: the object that I pass to the next ViewController is selected in the custom objects array using as index [indexPath.row], but indexPath.row starts at 0 for each section, so when I select an object its index in the TableView is not equal to the index in the custom objects array, meaning that I pass the wrong object.
Now, I'm stuck because I don't see a way to pass the right (meaning, selected) object to the next View Controller while preserving the fact that I'm working with only a single array.
I was toying with the idea of adding an objectIDString property to every object and a single var currentlySelectedObjectIDString that is set every time a cell is selected and try to pass to the next View Controller the object with the objectIDString property matching the currentlySelectedObjectIDString, but it looks like a bad idea to my inexperienced eyes and I'm actually not sure how I could accomplish that even if I wanted to (maybe implementing didSelectRowAtIndexPath:, but I have not been able to make it work).
Any help would be really appreciated, I've been stuck on this for so long I begin to question a) my sanity b)every decision I made so far in the project (meaning, the single array for all objects that is filtered in sections), but I'm already so invested in it that I really would like not to have to start over.
Thank you,
Cesare
P.S. I hope my question is clear, english isn't my main language... sorry for any mistake!
I suggest you, to use a NSFetchedResultsController. This class have a property sectionNameKeyPath. In this property you could set your category and you won't need more iterate with a repetition in each numberOfSection and numberOfRows.
like this:
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "event.startDate", cacheName: nil)
In my case i need filter data by event.startDate.
I don't know if you are using core data, but if you are using, this is the better way to do this.
I'll expose them for you!
In the first moment NSFetchedResultsController like complicated, but its very very useful. Don't be afraid.
I don't know exactly your model and data. In this case i'll show you my owner sample.
Please see my question in the following link:
Sectioning TableView and rows with Core Data Swift
In this link, see my question, and in the bottom i'll explain the complete solution with the others answer.
If this is not clear for you, please, talk with me :.)
I spent all day trying to figure out a way to solve my own question above and I think I've finally found a working-workaround.
My premise and disclaimer is that this is a pile of hacks, I post this only in case this might help someone in my situation in the future, but clearly the way to deal with this kind of situation is Core Data, as suggested by Weles' answer, not what I did.
Here's briefly what I've done to get my multi-component UITableView, in which all the data come from a single array of custom objects that is filtered by a different value in every component, to pass the selected object to the detail view when a cell is selected.
1) I added to all my customObjects an objectID : String computed property (current date + random number).
2) I added a var currentlySelectedObjectID : String? in my TableViewController.
3) I subclassed UITableViewCell, creating a CustomTableViewCell class that only adds to the normal class a var selectedCellID : String?, then I changed my cellForRowAtIndexPath to return a CustomTableViewCell instead of a UITableViewCell. Inside this method, before returning the cell, I also set the property selectedCellID of the cell equal to objectID of the current object. I also had to change the class of the cell in the Storyboard from UITableViewCell to CustomTableViewCell.
4) In the Storyboard I removed the segue from the cell to the detailViewController that was automatically created by Xcode and I set a custom StoryboardID to the detailViewController ("detailVC"),
5) Inside didSelectRowAtIndexPath of TableViewController I did all the work that before I was trying to do in prepareForSegue, but in a different way (not a segue, a self.navigationController?.pushViewController). Here's the code:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow();
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as CustomTableViewCell!;
self.currentlySelectedObjectID = currentCell.selectedCellID
// detailViewController instance
var detailVC = self.storyboard?.instantiateViewControllerWithIdentifier("detailVC") as MyDetailViewController
// I filter my objects array to "extract" the object with the objectID property equal to the currentlySelectedObjectID property (which is equal to the currentCell.selectedCellID, as set above). This array must have only 1 value. If so, I set the property passedCustomObject that I have in my detailViewController to the same object selected.
if (myObjectsArray!.filter() { $0.objectID == self.currentlySelectedObjectID }).count == 1 {
detailVC.passedCustomObject = (myObjectsArray!.filter() { $0.objectID == self.currentlySelectedObjectID })[0]
} else {
println("Error passing the object selected in the TableView to the DetailView")
}
// I push the detailViewController on top of the stack
self.navigationController?.pushViewController(detailVC, animated: true)
}
I think there are very good chance that a decent programmer (I am not one, but I hope to become one some day), seeing what I did, could faint.
Again, I don't think anyone should do this, if you're in my same situation go straight to Core Data: I spent a day on this, there's good chance that in three or four I could have had Core Data working.
But still, as hacked and inefficient as this is, it works... I tested multiple times. So, having spent so much time and having found no useful similar previous answers online, I thought to post mine.
Don't do this, I'm really afraid this is easily breakable! :)
I still look forward to other answers, to learn from my numerous mistakes!