Firebase function freezes app in xcode7, but works in xcode6.4 - ios

For some reason this code works perfectly in xcode6.4, but when switching to xcode7 it freezes the app.
What I am trying to do is pull the post information on a user's feed and display it on a tableview. I am able to pull the information from Firebase, but the app freezes before it displays on the tableview.
EDIT: The tableview works when I do not have any constraints or autolayout. It seems to not work when I try to have dynamic cell heights.
func getRadarData() {
let url = "https://(insert appname).firebaseio.com/users/" + currentUser + "/postsReceived/"
let targetRef = Firebase(url: url)
targetRef.observeEventType(.ChildAdded, withBlock: {
snapshot in
print("child")
if let found = self.posts.map({ $0.key }).indexOf(snapshot.key) {
let obj = self.posts[found]
print(obj)
print(found)
self.posts.removeAtIndex(found)
}
let postsUrl = "https://(insert appname).firebaseio.com/posts/" + snapshot.key
let postsRef = Firebase(url: postsUrl)
var updatedAt = snapshot.value["updatedAt"] as? NSTimeInterval
var endAt = snapshot.value["endAt"] as? NSTimeInterval
if updatedAt == nil {
updatedAt = 0
}
if endAt == nil {
endAt = 0
}
postsRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
if let key = snapshot.key
{if let content = snapshot.value["content"] as? String {
if let creator = snapshot.value["creator"] as? String {
if let createdAt = snapshot.value["createdAt"] as? NSTimeInterval {
let userurl = "https://(insert appname).firebaseio.com/users/" + (creator)
let userRef = Firebase(url: userurl)
userRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
if let username = snapshot.value["username"] as? String {
let updatedDate = NSDate(timeIntervalSince1970: (updatedAt!/1000))
let createdDate = NSDate(timeIntervalSince1970: (createdAt/1000))
let endedDate = NSDate(timeIntervalSince1970: (endAt!))
let post = Post(content: content, creator: creator, key: key, createdAt: updatedDate, name: username, joined: true, messageCount: 0, endAt: endedDate)
self.posts.append(post)
// Sort posts in descending order
self.posts.sortInPlace({ $0.createdAt.compare($1.createdAt) == .OrderedDescending })
self.tableView.reloadData()
}
})
}
}
}
}
})
})
}
Here is my code for my tableview where I used autolayout on the textView and nameLabel
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: RadarTableViewCell = tableView.dequeueReusableCellWithIdentifier("radarCell", forIndexPath: indexPath) as! RadarTableViewCell
let creator: (String) = posts[indexPath.row].creator
let key = posts[indexPath.row].key
let radarContent: (AnyObject) = posts[indexPath.row].content
cell.textView.selectable = false
cell.textView.text = radarContent as? String
cell.textView.userInteractionEnabled = false
cell.textView.selectable = true
let radarCreator: (AnyObject) = posts[indexPath.row].name
cell.nameLabel.text = radarCreator as? String
return cell

The issue was that I had initial text in my textView. I deleted it on my Storyboard and my app works now.
Found the solution here: Why does a previously working Xcode project hang up in Xcode 7 when presenting a new UITableviewController Subclass?

Related

Swift sender UIButton change text

I have a follow/unfollow button and am accessing it by "sender". I am changing the text when the user taps it to follow or unfollow another user. Problem is that when it should show "unfollow" it is showing the default text used in storyboard. The button changes to "follow" as it should, but not "unfollow". Also, I have to use "sender: UIButton" because I am accessing the tableview cells "tag" for the right information.
#IBAction func followButton(_ sender: UIButton) {
//self.yourFollowing.removeAll()
//self.following.removeAll()
self.followingTableView.reloadData()
let accessData = self.yourFollowing[sender.tag].dataPass
let businessUid = accessData["uid"] as! String
let businessName = accessData["businessName"] as! String
let businessStreet = accessData["businessStreet"] as! String
let businessCity = accessData["businessCity"] as! String
let businessState = accessData["businessState"] as! String
let businessZip = accessData["businessZIP"] as! String
let businessPhone = accessData["businessPhone"] as! String
let businessLatitude = accessData["businessLatitude"] as! String
let businessLongitude = accessData["businessLongitude"] as! String
let businessWebsite = accessData["businessWebsite"] as! String
let businessFacebook = accessData["facebookURL"] as! String
let businessFoursquare = accessData["foursquareURL"] as! String
let businessGoogle = accessData["googleURL"] as! String
let businessInstagram = accessData["instagramURL"] as! String
let businessSnapchat = accessData["snapchatURL"] as! String
let businessTwitter = accessData["twitterURL"] as! String
let businessYelp = accessData["yelpURL"] as! String
let userID = Auth.auth().currentUser!.uid
let ref = Database.database().reference()
let key = ref.child("Businesses").childByAutoId().key
var isFollower = false
let followersRef = "followers/\(businessUid)/\(self.loggedInUserData?["uid"] as! String)"
let followingRef = "following/" + (self.loggedInUserData?["uid"] as! String) + "/" + (businessUid)
ref.child("Businesses").child(userID).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (item, value) in following {
if value as! String == businessUid {
isFollower = true
let followersRef = "followers/\(businessUid)/\(self.loggedInUserData?["uid"] as! String)"
let followingRef = "following/" + (self.loggedInUserData?["uid"] as! String) + "/" + (businessUid)
let childUpdates = [followingRef:NSNull(),followersRef:NSNull()]
self.databaseRef.updateChildValues(childUpdates)
ref.child("Businesses").child(userID).child("following/\(item)").removeValue()
ref.child("Businesses").child(businessUid).child("followers/\(item)").removeValue()
sender.titleLabel?.text = "follow"
//self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
}
}
}
// Follow
if !isFollower {
sender.titleLabel?.text = "unfollow"
let followersData = ["email":self.loggedInUserData?["email"] as! String, "businessName":self.loggedInUserData?["businessName"] as! String]
let followingData = ["businessName":businessName, "businessStreet":businessStreet, "businessCity":businessCity, "businessState":businessState, "businessZIP":businessZip, "businessPhone":businessPhone, "businessWebsite":businessWebsite,"businessLatitude":businessLatitude, "businessLongitude":businessLongitude, "facebookURL":businessFacebook, "twitterURL":businessTwitter, "instagramURL":businessInstagram, "googleURL":businessGoogle, "yelpURL":businessYelp, "foursquareURL":businessFoursquare, "snapchatURL":businessSnapchat, "uid":businessUid]
let childUpdates = [followersRef:followersData, followingRef:followingData]
self.databaseRef.updateChildValues(childUpdates)
let following = ["following/\(key)" : businessUid]
let followers = ["followers/\(key)" : userID]
ref.child("Businesses").child(userID).updateChildValues(following as Any as! [AnyHashable : Any])
ref.child("Businesses").child(businessUid).updateChildValues(followers)
self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
}
})
}
Your problem is this line in your button action
#IBAction func followButton(_ sender: UIButton) {
.
.
var isFollower = false
.
.
}
You are declaring the variable isFollow inside the button action. This means that every time regardless of follow or unfollow, your isFollower is false which is why the condition for follow will work. But the change to true which is inside the completion of follow won't reflect next time you click the button because you are resetting isFollower to false.
Solution: Move the variable isFollow outside the button action.
var isFollow = false
#IBAction func followButton(_ sender: UIButton) {
// Your logic
}
Also your logic inside the completion seems wrong. Something like the code below should be there to change it to false.
if value as! String == businessUid {
isFollower = !isFollower
if isFollower {
// Follow logic
sender.setTitle("unfollow", for: .normal)
} else {
// Unfollowed logic
sender.setTitle("follow", for: .normal)
}
// Reload table
}

get data from firebase children

I have two custom cells in one table view.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Configure the cell...
if (indexPath.row == 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "Main", for: indexPath) as! PostTableViewCell
//Configure the cell
cell.PostView.layer.cornerRadius = 5
cell.PostView.layer.masksToBounds = false
cell.PostView.layer.shadowColor = UIColor.black.withAlphaComponent(0.4).cgColor
cell.PostView.layer.shadowOffset = CGSize(width: 0, height: 0)
cell.PostView.layer.shadowOpacity = 0.9
let post = Comments[indexPath.row] as! [String: AnyObject]
let commentname = post["author"] as? String
sendAuthor = post["author"] as? String
cell.CommentersName.setTitle(commentname, for: .normal)
if let seconds = post["pub_time"] as? Double {
let timeStampDate = NSDate(timeIntervalSince1970: seconds/1000)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d, yyyy"
let formating = timeStampDate as Date
cell.CommentTime.text = dateFormatter.string(from: formating)
}
cell.comment.text = post["content"] as? String
textViewDidChange(cell.comment)
cell.comment.frame.size.width = 344
cell.comment.sizeToFit()
cell.comment.clipsToBounds = true
cell.REply.frame.origin.y = cell.comment.frame.maxY + 10
cell.PostView.frame.size.height = cell.comment.frame.maxY + 50
TableView.rowHeight = cell.PostView.frame.size.height + 20
cell.LikesNumber.text = post["num_likes"] as? String
replyId = post["id"] as? String
cell.checkfornightmode()
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "Reply", for: indexPath) as! RepliesTableViewCell
cell.ReplyCustomCell.layer.cornerRadius = 5
cell.ReplyCustomCell.layer.masksToBounds = false
cell.ReplyCustomCell.layer.shadowColor = UIColor.black.withAlphaComponent(0.4).cgColor
cell.ReplyCustomCell.layer.shadowOffset = CGSize(width: 0, height: 0)
cell.ReplyCustomCell.layer.shadowOpacity = 0.9
let post = Comments[indexPath.row] as! [String: AnyObject]
let posttest = post["id"] as? String
let replyRef = Database.database().reference().child("main").child("posts").child(postID!).child("comments").child(posttest!).child("comments")
replyRef.observeSingleEvent(of: .value, with: { (snapshot:DataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for testingkey in postsDictionary.keys {
Database.database().reference().child("main").child("posts").child(self.postID!).child("comments").child(posttest!).child("comments").child(testingkey).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let content : String? = value?["content"] as? String ?? ""
cell.ReplyText.text = content!
})
}
}
})
TableView.rowHeight = 150.0
return cell
}
}
The first cell with the identifier main is supposed to print out all the intial comments for a certain post. The second cell with the identifier is supposed to print out the comments to main comments. Based on this code, I am only getting the last comment of the main post and the last comment to the main posts.
This is what the json looks like
First thing is to separate the firebase call into a separate function and in that function populate a dictionary with posts as key and comments as value
for example like this
var postComments: [String: [String]] = [:] // post as key and string array as comments
In firebase database call populate this with snapshot.
After the data call but within the firebase Database call back use this function to reload data
DispatchQueue.main.async{
tableView.reloadData()
}
Set the cell with postComments array.

How to filter an array of JSON objects to be used in a table view?

I'm getting JSON data from an API and parsing that data in objects, which are then simply stored in an array of objects. The objects themselves contain data about articles from a newspaper. However, I need to filter that data. Some of the objects I'm getting from my JSON actually have no article content because they are pictures and not articles (i.e. some of the "nodes" from the API's JSON have content that I don't want to see in my table view).
In my JSON-parsing function, I've tried to make it so that the parsed object will only get added to the array of parsed objects if the character count of the "articleContent" variable is above 40. Here is what it looked like.
if issueElement.articleContent.characters.count > 40 {
self.currentIssueObjects.addObject(issueElement)
}
However, this simply does not work. I get the typical "unexpectedly found nil while unwrapping an Optional value" error message (I don't get a specific line for the error). How can I make this work ? I'm essentially trying to prevent the array from having objects with empty articleContent, because then that screws up my table view (empty cells, duplicates, etc...).
Here is my cellForRowAtIndexPath code, and my JSON-parsing code:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell {
let row = indexPath.row
guard let cell = tableView.dequeueReusableCellWithIdentifier(CurrentIssueArticlesTableCellIdentifier, forIndexPath: indexPath) as? CurrentIssueArticlesTableViewCell else {
print ("error: currentIssueTableView cell is not of class CurrentIssueArticlesTableViewCell, we will use EditorialsTableViewCell instead")
return tableView.dequeueReusableCellWithIdentifier(CurrentIssueArticlesTableCellIdentifier, forIndexPath: indexPath) as! EditorialsTableViewCell
}
let currentIssueObject = currentIssueObjects.objectAtIndex(indexPath.row) as! IssueElement
let title = currentIssueObject.title ?? ""
let timeStampDateObject = NSDate(timeIntervalSince1970: NSTimeInterval(currentIssueObject.timeStamp))
let timeStampDateString = dateFormatter.stringFromDate(timeStampDateObject) ?? "Date unknown"
if let author = currentIssueObject.author {
cell.currentIssueArticlesAuthorLabel!.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.currentIssueArticlesAuthorLabel!.text = author
}
let issueNumber = currentIssueObject.issueNumber ?? ""
let volumeNumber = currentIssueObject.volumeNumber ?? ""
let articleContent = currentIssueObject.articleContent ?? ""
let nodeID = currentIssueObject.nodeID ?? 0
cell.currentIssueArticlesHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.currentIssueArticlesHeadlineLabel.text = title
cell.currentIssueArticlesPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.currentIssueArticlesPublishDateLabel.text = timeStampDateString
if row == 0 {
cell.userInteractionEnabled = false
let imageURL = (currentIssueObjects.objectAtIndex(row) as! IssueElement).imageURL
cell.currentIssueArticlesHeadlineLabel.textColor = UIColor.clearColor()
cell.currentIssueArticlesAuthorLabel.textColor = UIColor.clearColor()
cell.currentIssueArticlesPublishDateLabel.textColor = UIColor.clearColor()
cell.request?.cancel()
if let image = self.imageCache.objectForKey(imageURL!) as? UIImage {
cell.currentIssueArticlesBackgroundImageView.image = image
} else {
cell.currentIssueArticlesBackgroundImageView.image = UIImage(named: "reveal Image")
cell.request = Alamofire.request(.GET, imageURL!).responseImage() { response in
if response.result.error == nil && response.result.value != nil {
self.imageCache.setObject(response.result.value!, forKey: response.request!.URLString)
cell.currentIssueArticlesBackgroundImageView.image = response.result.value
} else {
}
}
}
cell.currentIssueArticlesBackgroundImageView.hidden = false
}
else {
cell.currentIssueArticlesBackgroundImageView.hidden = true
}
return cell
}
JSON-parsing code:
func populateCurrentIssue() {
if populatingCurrentIssue {
return
}
populatingCurrentIssue = true
self.cellLoadingIndicator.backgroundColor = goldenWordsYellow
self.cellLoadingIndicator.startAnimating()
Alamofire.request(GWNetworking.Router.Issue).responseJSON() { response in
if let JSON = response.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
var nodeIDArray : [Int]
if (JSON .isKindOfClass(NSDictionary)) {
for node in JSON as! Dictionary<String, AnyObject> {
let nodeIDValue = node.0
var lastItem : Int = 0
self.nodeIDArray.addObject(nodeIDValue)
if let issueElement : IssueElement = IssueElement(title: "Could not retrieve title", nodeID: 0, timeStamp: 0, imageURL: "init", author: "Author not found", issueNumber: "Issue # error", volumeNumber: "Volume # error", articleContent: "Could not retrieve article content", coverImageInteger: "init", coverImage: UIImage()) {
issueElement.title = node.1["title"] as! String
issueElement.nodeID = Int(nodeIDValue)!
let timeStampString = node.1["revision_timestamp"] as! String
issueElement.timeStamp = Int(timeStampString)!
issueElement.imageURL = String(node.1["image_url"])
if let author = node.1["author"] as? String {
issueElement.author = author
}
if let issueNumber = node.1["issue_int"] as? String {
issueElement.issueNumber = issueNumber
}
if let volumeNumber = node.1["volume_int"] as? String {
issueElement.volumeNumber = volumeNumber
}
if let articleContent = node.1["html_content"] as? String {
issueElement.articleContent = articleContent
}
issueElement.coverImageInteger = String(node.1["cover_image"]) // addition specific to the Current Issue View Controller
lastItem = self.currentIssueObjects.count
print(issueElement.nodeID)
if issueElement.articleContent.characters.count > 40 {
self.currentIssueObjects.addObject(issueElement)
print(issueElement.nodeID)
}
// Sorting with decreasing timestamp from top to bottom.
let timestampSortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
self.currentIssueObjects.sortUsingDescriptors([timestampSortDescriptor])
// Placing the object with coverImage
let coverImageSortDescriptor = NSSortDescriptor(key: "coverImageInteger", ascending: false)
self.currentIssueObjects.sortUsingDescriptors([coverImageSortDescriptor])
let indexPaths = (lastItem..<self.currentIssueObjects.count).map {
NSIndexPath(forItem: $0, inSection: 0) }
}
}
}
dispatch_async(dispatch_get_main_queue()) {
self.currentIssueTableView.reloadData()
self.cellLoadingIndicator.stopAnimating()
self.cellLoadingIndicator.hidesWhenStopped = true
}
}
}
self.populatingCurrentIssue = false
}
}

[__NSOrderedSetM objectAtIndex:]: Index 10 beyond bounds [ 0 .. 9[

I changed my main data structure containing all of my JSON objects to be used in my table view from a NSMutableArray to a NSMutableOrderedSet. Now, my app loads the first ten objects from the API correctly but the second I want to scroll down, it crashes. Any suggestions ?
override func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.y + view.frame.size.height > scrollView.contentSize.height * 0.8) {
populateEditorials()
}
}
func populateEditorials() {
if populatingEditorials {
return
}
populatingEditorials = true
Alamofire.request(GWNetworking.Router.Editorials(self.currentPage)).responseJSON() { response in
if let JSON = response.result.value {
/*
if response.result.error == nil {
*/
/* Creating objects for every single editorial is long running work, so we put that work on a background queue, to keep the app very responsive. */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
// Making an array of all the node IDs from the JSON file
var nodeIDArray : [Int]
/* var nodeCounter : Int = 0 */
if (JSON .isKindOfClass(NSDictionary)) {
for node in JSON as! Dictionary<String, AnyObject> {
var nodeIDValue = node.0
var lastItem : Int = 0
self.nodeIDArray.addObject(nodeIDValue)
if var editorialElement : EditorialElement = EditorialElement(title: "init", nodeID: 0, timeStamp: 0, imageURL: "init", author: "init", issueNumber: "init", volumeNumber: "init", articleContent: "init") {
editorialElement.title = node.1["title"] as! String
editorialElement.nodeID = Int(nodeIDValue)
let timeStampString = node.1["revision_timestamp"] as! String
editorialElement.timeStamp = Int(timeStampString)!
editorialElement.imageURL = String(node.1["image_url"])
editorialElement.author = String(node.1["author"])
editorialElement.issueNumber = String(node.1["issue_int"])
editorialElement.volumeNumber = String(node.1["volume_int"])
editorialElement.articleContent = String(node.1["html_content"])
lastItem = self.editorialObjects.count
self.editorialObjects.addObject(editorialElement)
/* Sorting the elements in order of newest to oldest (as the array index increases] */
self.editorialObjects.sort({ $0.timeStamp > $1.timeStamp })
let indexPaths = (lastItem..<self.editorialObjects.count).map { NSIndexPath(forItem: $0, inSection: 0) }
dispatch_async(dispatch_get_main_queue()) {
self.editorialsTableView.reloadData()
// self.editorialsTableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Automatic) // Animation implemented for testing, to be removed for version 1.0
}
/*
nodeIDArray[nodeCounter] = jsonValue{nodeCounter}.string
let editorialInfos : EditorialElement = ((jsonValue as! NSDictionary].1["\(nodeIDArray[nodeCounter]]"] as! [NSDictionary]].map { EditorialElement(title: $0["title"] as! String, nodeID: $0["nid"] as! Int, timeStamp: $0["revision_timestamp"] as! Int, imageURL: $0["image_url"] as! String, author: $0["author"], issueNumber: $0["issue_int"] as! Int, volumeNumber: $0["volume_int"] as! Int, articleContent: $0["html_content"] as! String] // I am going to try to break this line down to simplify it and fix the build errors
*/
}
print(self.editorialObjects.count)
}
}
/* Sorting the elements in order of newest to oldest (as the array index increases] */
self.editorialObjects.sort({ $0.timeStamp > $1.timeStamp })
let lastItem = self.editorialObjects.count
let indexPaths = (lastItem..<self.editorialObjects.count).map { NSIndexPath(forItem: $0, inSection: $0) }
dispatch_async(dispatch_get_main_queue()) {
self.editorialsTableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Automatic) // Animation implemented for testing, to be removed for version 1.0
}
self.currentPage++
} */
}
self.populatingEditorials = false
}
}
}
Why is my app crashing once I try to load the second set of 10 objects?
EDIT 1: cellForRowAtIndexPath code
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
/*
cell.editorialHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline]
cell.editorialHeadlineLabel.text = editorialHeadline[row]
cell.editorialAuthorLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline]
cell.editorialAuthorLabel.text = editorialAuthor[row]
cell.editorialPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline]
cell.editorialPublishDateLabel.text = editorialPublishDate[row]
*/
let cell = tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as! EditorialsTableViewCell
let title = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).title
let timeStamp = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).timeStamp
let imageURL = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).imageURL
let author = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).author
let issueNumber = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).issueNumber
let volumeNumber = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).volumeNumber
let articleContent = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).articleContent
/* Unlike the Pictures Collection View, there is no need to create another Alamofire request here, since we already have all the content we want from the JSON we downloaded. There is no URL that we wish to place a request to to get extra content. */
cell.editorialHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.editorialHeadlineLabel.text = title
cell.editorialAuthorLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.editorialAuthorLabel.text = author
cell.editorialPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.editorialPublishDateLabel.text = String(timeStamp)
cell.editorialVolumeAndIssueLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.editorialVolumeAndIssueLabel.text = "Volume \(volumeNumber) - Issue \(issueNumber)"
return cell
}
EDIT 2: Screenshot of editorialObjects.count as I scroll down the view.

Refactor cellForRowIndexPath in UITableView Swift

I have a rather long cellForRowAtIndexPath function. I am using parse as my backend and have a lot going on. I want to extract a lot of these conditions and put them in their own functions. Especially the PFUser query, but unfortunately I don't know whats the best way to go about it since I don't know how I can access the elements of each cell in those functions I want to write.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PostCells", forIndexPath: indexPath) as! NewsFeedTableCellTableViewCell
// Configure the cell...
// A drive is a post
let drive: PFObject = self.timelineData[indexPath.row] as PFObject
var driverId = drive.objectForKey("driver")!.objectId!
var currentUserObjectId = PFUser.currentUser()!.objectId
if(driverId != currentUserObjectId){
cell.requestButton.layer.borderWidth = 1
cell.requestButton.titleLabel!.font = UIFont.systemFontOfSize(11)
cell.requestButton.tintColor = UIColor.orangeColor()
cell.requestButton.layer.borderColor = UIColor.orangeColor().CGColor
cell.requestButton.setTitle("REQUEST", forState: UIControlState.Normal)
}
else {
cell.requestButton.layer.borderWidth = 1
cell.requestButton.titleLabel!.font = UIFont.systemFontOfSize(11)
cell.requestButton.tintColor = UIColor.grayColor()
cell.requestButton.layer.borderColor = UIColor.lightGrayColor().CGColor
cell.requestButton.setTitle("REQUEST", forState: UIControlState.Normal)
cell.requestButton.enabled = false
}
// Setting up the attributes of the cell for the news feed
cell.driveTitleTextField.text = drive.objectForKey("title") as! String
cell.wayTextField.text = drive.objectForKey("way") as! String
var departureDate = NSDate()
departureDate = drive.objectForKey("departureDate") as! NSDate
var dateFormat = NSDateFormatter()
dateFormat.dateFormat = "M/dd hh:mm a"
cell.departureDateTextField.text = dateFormat.stringFromDate(departureDate)
if((drive.objectForKey("way")!.isEqualToString("Two Way")))
{
var returnDate = NSDate()
returnDate = drive.objectForKey("returnDate") as! NSDate
cell.returningDateTextField.text = dateFormat.stringFromDate(returnDate)
}
else if((drive.objectForKey("way")!.isEqualToString("One Way")))
{
cell.returningDateTextField.enabled = false
cell.returningDateTextField.userInteractionEnabled = false
cell.returningDateTextField.hidden = true
cell.returningLabel.hidden = true
}
var seatNumber = NSNumber()
seatNumber = drive.objectForKey("seatNumber") as! NSInteger
var numberFormat = NSNumberFormatter()
numberFormat.stringFromNumber(seatNumber)
cell.seatNumberTextField.text = numberFormat.stringFromNumber(seatNumber)
// this is a PFUser query so we can get the users image and name and email from the User class
var findDrive = PFUser.query()
var objectId: AnyObject? = drive.objectForKey("driver")!.objectId!
findDrive?.whereKey("objectId", equalTo: objectId!)
findDrive?.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]?, error:NSError?)->Void in
if (error == nil){
if let actualObjects = objects {
let possibleUser = (actualObjects as NSArray).lastObject as? PFUser
if let user = possibleUser {
cell.userProfileNameLabel.text = user["fullName"] as? String
cell.userEmailLabel.text = user["username"] as? String
//Profile Image
cell.profileImage.alpha = 0
if let profileImage = user["profilePicture"] as? PFFile {
profileImage.getDataInBackgroundWithBlock{
(imageData:NSData? , error:NSError?)-> Void in
if(error == nil) {
if imageData != nil{
let image:UIImage = UIImage (data: imageData!)!
cell.profileImage.image = image
}
}
}
}
UIView.animateWithDuration(0.5, animations: {
cell.driveTitleTextField.alpha = 1
cell.wayTextField.alpha = 1
cell.profileImage.alpha = 1
cell.userProfileNameLabel.alpha = 1
cell.userEmailLabel.alpha = 1
cell.seatNumberTextField.alpha = 1
cell.returningDateTextField.alpha = 1
cell.departureDateTextField.alpha = 1
})
}
}
}
}
return cell
}
EDIT 1
I came up with a way to refactor my code that I would like critiqued!
1. I extracted a lot of the cells configurations and put them into to functions, one for the button on the cell and the other for all the data from parse.
func configureDataTableViewCell(cell:NewsFeedTableCellTableViewCell, drive: PFObject)
{
cell.driveTitleTextField.text = drive.objectForKey("title") as! String
cell.wayTextField.text = drive.objectForKey("way") as! String
cell.userEmailLabel.text = drive.objectForKey("username") as? String
cell.userProfileNameLabel.text = drive.objectForKey("name") as? String
var departureDate = NSDate()
departureDate = drive.objectForKey("departureDate") as! NSDate
var dateFormat = NSDateFormatter()
dateFormat.dateFormat = "M/dd hh:mm a"
cell.departureDateTextField.text = dateFormat.stringFromDate(departureDate)
if((drive.objectForKey("way")!.isEqualToString("Two Way")))
{
var returnDate = NSDate()
returnDate = drive.objectForKey("returnDate") as! NSDate
cell.returningDateTextField.text = dateFormat.stringFromDate(returnDate)
}
else if((drive.objectForKey("way")!.isEqualToString("One Way")))
{
cell.returningDateTextField.enabled = false
cell.returningDateTextField.userInteractionEnabled = false
cell.returningDateTextField.hidden = true
cell.returningLabel.hidden = true
}
var seatNumber = NSNumber()
seatNumber = drive.objectForKey("seatNumber") as! NSInteger
var numberFormat = NSNumberFormatter()
numberFormat.stringFromNumber(seatNumber)
cell.seatNumberTextField.text = numberFormat.stringFromNumber(seatNumber)
}
func configureButtonTableViewCell(cell:NewsFeedTableCellTableViewCell, userID: String)
{
var currentUserObjectId = PFUser.currentUser()!.objectId
if(userID != currentUserObjectId){
cell.requestButton.layer.borderWidth = 1
cell.requestButton.titleLabel!.font = UIFont.systemFontOfSize(11)
cell.requestButton.tintColor = UIColor.orangeColor()
cell.requestButton.layer.borderColor = UIColor.orangeColor().CGColor
cell.requestButton.setTitle("REQUEST", forState: UIControlState.Normal)
println("orange")
}
else {
cell.requestButton.layer.borderWidth = 1
cell.requestButton.titleLabel!.font = UIFont.systemFontOfSize(11)
cell.requestButton.tintColor = UIColor.grayColor()
cell.requestButton.layer.borderColor = UIColor.lightGrayColor().CGColor
cell.requestButton.setTitle("REQUEST", forState: UIControlState.Normal)
cell.requestButton.enabled = false
println("gray")
}
}
2. I then passed in the functions from step 1 and into my cellForRowIndexPath
// A drive is a post
let drive: PFObject = self.timelineData[indexPath.row] as PFObject
var driverId : String = drive.objectForKey("driver")!.objectId!!
configureButtonTableViewCell(cell, userID: driverId)
configureDataTableViewCell(cell, drive: drive)
3. I stored all my PFUser data into my object when its saved instead of querying the user class. So I get the PFUser.currentUser() username, full name, and profile picture when they save a post.
My load data has been modified. I store all the profile pictures in there own array.
func loadData(){
var findItemData:PFQuery = PFQuery(className:"Posts")
findItemData.addDescendingOrder("createdAt")
findItemData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]? , error:NSError?) -> Void in
if error == nil
{
self.timelineData.removeAll(keepCapacity: false)
self.profilePictures.removeAll(keepCapacity: false)
self.timelineData = objects as! [PFObject]
for object in objects! {
self.profilePictures.append(object.objectForKey("profilePicture") as! PFFile)
}
self.newsFeedTableView.reloadData()
}
}
}
And finally, here is my updated cellForRowIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("PostCells", forIndexPath: indexPath) as! NewsFeedTableCellTableViewCell
// Configure the cell...
// A drive is a post
let drive: PFObject = self.timelineData[indexPath.row] as PFObject
var driverId : String = drive.objectForKey("driver")!.objectId!!
configureButtonTableViewCell(cell, userID: driverId)
configureDataTableViewCell(cell, drive: drive)
println(PFUser.currentUser()?.objectForKey("username"))
if let profileImage = drive["profilePicture"] as? PFFile {
profileImage.getDataInBackgroundWithBlock{
(imageData:NSData? , error:NSError?)-> Void in
if(error == nil) {
if imageData != nil{
let image:UIImage = UIImage (data: imageData!)!
cell.profileImage.image = image
}
}
}
}
return cell
}
Let me know what you guys think, I want to do make my code much more readable, fast, and memory efficient.
You shouldn't be doing any heavy model stuff inside cellForRow.
What you're currently trying to do will greatly slow down your UI.
In most cases you will want your model objects setup, and ready to go before you even get to cellForRow.
This means performing your Parse queries somewhere like in viewDidLoad, keep those results in an array, and when it comes time to do so, apply them to your cells in cellForRow. This way, when a user scrolls, a new query won't be dispatched for every new cell that comes into view. It will already be available.
In addition to this, should you need to make any changes to these items once they have been fetched, you can do so, and have them remain unchanged even when the user is scrolling.
Refactor so you have some data type or group of instance variables to serve as a view model. Avoid making asynchronous calls that mutate the cell in cellForRowAtIndexPath. Instead have your data access method mutate or recreate the view model and at the end of your callback, dispatch_async to the main queue. Give it a closure that tells your table view to reloadData and whatever else you need to do for views to show the new data.
Here's a little pseudocode to describe what I mean:
func loadData() {
parseQueryWithCallback() { data in
self.viewModel = doWhateverTransformsAreNeeded(data)
dispatch_async(dispatch_get_main_queue(), self.tableView.reloadData)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) {
let cell = dequeue(...)
cell.thingOne = self.viewModel.things[indexPath.row].thingOne
cell.thingTwo = self.viewModel.things[indexPath.row].thingTwo
return cell
}

Resources