Reload TableView only at new rows once user scrolls to bottom - swift

I am trying to append new data to Tableview and only reload the Tableview for the new data added.
My app has been crashing giving me this error: "attempt to delete row 19 from section 0 which only contains 10 rows before the update'"
I would like to reload those rows once the asynchronous function contentQueryContinuous() is completed.
Here is my code:
//Once User Scrolls all the way to bottom, beginContentBatchFetch() is called
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentSize.height
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.height {
beginContentBatchFetch()
}
}
//Content Batch Fetch looks up to 10 new elements, tries to append to current array's, and then reload's tableview only for those new elements
func beginContentBatchFetch() {
contentFetchMore = true
ProgressHUD.show()
let oldcount = contentObjectIdArray.count
var IndexPathsToReload = [IndexPath]()
var startIndex = Int()
var endIndex = Int()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
//Calls self.contentQueryContinous which adds new elements
self.contentQueryContinous()
let newElements = self.contentObjectIdArray.count - oldcount
startIndex = oldcount
endIndex = self.contentObjectIdArray.count
for index in startIndex..<endIndex {
let indexPath = IndexPath(row: index, section: 0)
IndexPathsToReload.append(indexPath)
}
if newElements > 0 {
self.MyTableView.reloadRows(at: IndexPathsToReload, with: .fade)
}
ProgressHUD.dismiss()
}
}
Here is contentQueryContinous()
func contentQueryContinous() {
if contentObjectIdArray.count != 0 {
contentSkip = contentObjectIdArray.count
let query = PFQuery(className: "ContentPost")
query.whereKey("Spot", equalTo: SpotText)
query.limit = 10
query.skip = contentSkip
query.addDescendingOrder("createdAt")
query.findObjectsInBackground(block: { (objects: [PFObject]?,error: Error?) in
if let objects = objects {
for object in objects {
let ProfileImageFile = object["ProfileImage"] as? PFFileObject
let urlString = ProfileImageFile?.url as! String
if let url = URL(string: urlString) {
let data = try? Data(contentsOf: url)
if let imageData = data {
self.contentPostProPicUrlArray.append(urlString as NSString)
self.contentPostProPicImageCache.setObject(UIImage(data: imageData)!, forKey: urlString as NSString)
}
}
if object["Post"] != nil && object["UserLikes"] != nil && object["Username"] != nil && object["UserTime"] != nil {
self.contentPostArray.append(object["Post"] as! String)
self.contentLikeArray.append(object["UserLikes"] as! Int)
self.contentUsernameArray.append(object["Username"] as! String)
self.contentTimeArray.append(object["UserTime"] as! String)
self.contentObjectIdArray.append(object.objectId!)
}
}
print(self.contentPostArray)
}
})
}
}

You are trying to reload a cell that is not on the table. Try inserting it. Also, you are risking a race condition.
From the docs
Reloading a row causes the table view to ask its data source for a new cell for that row. The table animates that new cell in as it animates the old row out. Call this method if you want to alert the user that the value of a cell is changing.

Related

CollectionView Reloaddata - Fatal error: Index out of range

I add the data that I have drawn from Database to CollectionView. I am putting the data I have added as an array in the model array. I see the data inside Array in collectionView. Sometimes data is added smoothly but sometimes I get the error
"Thread 1: Fatal error: Index out of range"
. Sometimes while working sometimes why not? I think there is a problem with collectionView.reloadData ().
enter image description here
#IBOutlet weak var sonsuzCollec: UICollectionView!
var model = [[String]]()
var davetiyefilee = [String]()
var davetiyefilee2 = [String]()
extension denemeView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
if (collectionView == sonsuzCollec) {
return model[section].count
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
if (collectionView == sonsuzCollec) {
return yeniDavKATIsımNew.count
}
return 0
}
...
}
#objc func davetiyeCEK1() {
if let baslik = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
for review in baslik {
if let soru_baslik = review["davetiyefilee"] as? String {
let s = String(describing: soru_baslik)
self.davetiyefilee.append(s)
}
}
self.model.append(self.davetiyefilee)
DispatchQueue.main.async { [weak self] in
self?.sonsuzCollec?.reloadData()
}
}
}
#objc func davetiyeCEK2() {
if let baslik = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
for review in baslik {
if let soru_baslik = review["davetiyefilee"] as? String {
let s = String(describing: soru_baslik)
self.davetiyefilee2.append(s)
}
}
self.model.append(self.davetiyefilee2)
DispatchQueue.main.async { [weak self] in
self?.sonsuzCollec?.reloadData()
}
}
}
i think it is beacuse of your model array's section item is empty.
how many collection you are using? can you show more full code
or maybe another approch is in your numberofsection try this
if (collectionView == sonsuzCollec) {
var numberofRows = 0
if model[section].count > 0 {
numberofRows = model[section].count
} else {
numberofRows = 0
}
return numberofRows
}

Firebase pagination with CollectionView always loads the same image

I am trying to implement a pagination in my CollectionView.
Right now, I always get the same picture, when I call my function loadMorePosts
I am sure it is because I don't revert the snapshot of my data, so the image will always be on top.
How do I reverse my snapshot to get the correct data?
This is my code to add new data:
func loadMorePosts() {
var numOfItems = posts.count
let preNumOfItems = numOfItems
numOfItems += 1
let REF_POST = Database.database().reference().child("posts")
let REF_QUERY = REF_POST.queryOrdered(byChild: "postDate").queryLimited(toLast: UInt(numOfItems))
REF_QUERY.observe(.value) { (snapshot) in
var newPost = [PostModel]()
var count = 0
for post in snapshot.children {
let data = post as! DataSnapshot
print("num of items " + String(numOfItems))
print("counter " + String(count))
let lostPost = PostModel(dictionary: data.value as! [String : Any], key: snapshot.key)
if count >= Int(preNumOfItems) {
newPost.insert(lostPost, at: 0)
}
count+=1
}
self.posts += newPost
self.exploreCollectionView.reloadData()
}
}

Reloading individual TableView rows upon changes in document

Long time listener, first time app developer..
I'm using Firestore data to populate a TableView in Swift 4.2 using a snapshot listener. This works great if I don't mind the entire TableView reloading with every document change, however I've now added animations to the cell that trigger upon a status value change in the document and my present implementation of tableView.reloadData() triggers all cells to play their animations with any change to any document in the collection.
I'm seeking help understanding how to implement reloadRows(at:[IndexPath]) using .documentChanges with diff.type == .modified to reload only the rows that have changed and have spent more time than I'd like to admit trying to figure it out. =/
I have attempted to implement tableView.reloadRows, but cannot seem to understand how to specify the indexPath properly for only the row needing updated. Perhaps I need to add conditional logic for the animations to only execute with changes in the document? Losing hair.. Any help is greatly appreciated.
Snapshot implementation:
self.listener = query?.addSnapshotListener(includeMetadataChanges: true) { documents, error in
guard let snapshot = documents else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let source = snapshot.metadata.isFromCache ? "local cache" : "server"
print("Metadata: Data fetched from \(source)")
let results = snapshot.documents.map { (document) -> Task in
if let task = Task(eventDictionary: document.data(), id: document.documentID) {
return task
} // if
else {
fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")
} // else
} //let results
self.tasks = results
self.documents = snapshot.documents
self.tableView.reloadData()
} // if added
if (diff.type == .modified) {
print("Modified document: \(diff.document.data())")
let results = snapshot.documents.map { (document) -> Task in
if let task = Task(eventDictionary: document.data(), id: document.documentID) {
return task
} // if
else {
fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")
} // else closure
} //let closure
self.tasks = results
self.documents = snapshot.documents
self.tableView.reloadData() // <--- reloads the entire tableView with changes = no good
self.tableView.reloadRows(at: <#T##[IndexPath]#>, with: <#T##UITableView.RowAnimation#>) // <-- is what I need help with
}
if (diff.type == .removed) {
print("Document removed: \(diff.document.data())")
} // if removed
} // forEach
} // listener
cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "eventListCell", for: indexPath) as! EventTableViewCell
let item = tasks[indexPath.row]
let url = URL.init(string: (item.eventImageURL))
datas.eventImageURL = url
cell.eventImageView.kf.setImage(with: url)
cell.eventEntranceLabel!.text = item.eventLocation
cell.eventTimeLabel!.text = item.eventTime
if item.eventStatus == "inProgress" {
cell.eventReponderStatus.isHidden = false
cell.eventReponderStatus.text = "\(item.eventResponder)" + " is responding"
UIView.animate(withDuration: 2, delay: 0.0, options: [.allowUserInteraction], animations: {cell.backgroundColor = UIColor.yellow; cell.backgroundColor = UIColor.white}, completion: nil)
}
else if item.eventStatus == "verifiedOK" {
cell.eventReponderStatus.isHidden = false
cell.eventReponderStatus.text = "\(item.eventResponder)" + " verified OK"
UIView.animate(withDuration: 2, delay: 0.0, options: [.allowUserInteraction], animations: {cell.backgroundColor = UIColor.green; cell.backgroundColor = UIColor.white}, completion: nil)
}
else if item.eventStatus == "sendBackup" {
cell.eventReponderStatus.isHidden = false
cell.eventReponderStatus.text = "\(item.eventResponder)" + " needs assistance"
UIView.animate(withDuration: 1, delay: 0.0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {cell.backgroundColor = UIColor.red; cell.backgroundColor = UIColor.white}, completion: nil)
}
else if item.eventStatus == "newEvent" {
UIView.animate(withDuration: 2, delay: 0.0, options: [.allowUserInteraction], animations: {cell.backgroundColor = UIColor.red; cell.backgroundColor = UIColor.white}, completion: nil)
}
else {
cell.isHidden = true
cell.eventReponderStatus.isHidden = true
}
switch item.eventStatus {
case "unhandled": cell.eventStatusIndicator.backgroundColor = UIColor.red
case "inProgress": cell.eventStatusIndicator.backgroundColor = UIColor.yellow
case "verifiedOK": cell.eventStatusIndicator.backgroundColor = UIColor.green
case "sendBackup": cell.eventStatusIndicator.backgroundColor = UIColor.red
default: cell.eventStatusIndicator.backgroundColor = UIColor.red
}
return cell
}
Variables and setup
// Create documents dictionary
private var documents: [DocumentSnapshot] = []
// Create tasks var
public var tasks: [Task] = []
// Create listener registration var
private var listener : ListenerRegistration!
// Create baseQuery function
fileprivate func baseQuery() -> Query {
switch switchIndex {
case 0:
return Firestore.firestore().collection("metalDetectorData").document("alarmEvents").collection("eventList").limit(to: 50).whereField("eventStatus", isEqualTo: "unhandled")
case 1:
return Firestore.firestore().collection("metalDetectorData").document("alarmEvents").collection("eventList").limit(to: 50).whereField("eventStatus", isEqualTo: "verifiedOK")
case 3:
return Firestore.firestore().collection("metalDetectorData").document("alarmEvents").collection("eventList").limit(to: 50)
default:
return Firestore.firestore().collection("metalDetectorData").document("alarmEvents").collection("eventList").limit(to: 50)//.whereField("eventStatus", isEqualTo: false)
}
} // baseQuery closure
// Create query variable
fileprivate var query: Query? {
didSet {
if let listener = listener {
listener.remove()
}
}
} // query closure
Tasks
struct Task{
var eventLocation: String
var eventStatus: String
var eventTime: String
var eventImageURL: String
var eventResponder: String
var eventUID: String
var eventDictionary: [String: Any] {
return [
"eventLocation": eventLocation,
"eventStatus": eventStatus,
"eventTime": eventTime,
"eventImageURL": eventImageURL,
"eventResponder": eventResponder,
"eventUID": eventUID
]
} // eventDictionary
} // Task
extension Task{
init?(eventDictionary: [String : Any], id: String) {
guard let eventLocation = eventDictionary["eventLocation"] as? String,
let eventStatus = eventDictionary["eventStatus"] as? String,
let eventTime = eventDictionary["eventTime"] as? String,
let eventImageURL = eventDictionary["eventImageURL"] as? String,
let eventResponder = eventDictionary["eventResponder"] as? String,
let eventUID = id as? String
else { return nil }
self.init(eventLocation: eventLocation, eventStatus: eventStatus, eventTime: eventTime, eventImageURL: eventImageURL, eventResponder: eventResponder, eventUID: eventUID)
}
}
So I did this without really knowing Firebase or having a compiler to check for errors. There may be some typos and you may have to do some unwrapping and casting but the idea should be there. I added lots of comments to help you understand what is happening in the code…
self.listener = query?.addSnapshotListener(includeMetadataChanges: true) { documents, error in
guard let snapshot = documents else {
print("Error fetching snapshots: \(error!)")
return
}
// You only need to do this bit once, not for every update
let source = snapshot.metadata.isFromCache ? "local cache" : "server"
print("Metadata: Data fetched from \(source)")
let results = snapshot.documents.map { (document) -> Task in
if let task = Task(eventDictionary: document.data(), id: document.documentID) {
return task
} // if
else {
fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")
} // else
} //let results
// Tell the table view you are about to give it a bunch of updates that should all get batched together
self.tableView.beginUpdates()
snapshot.documentChanges.forEach { diff in
let section = 0 // This should be whatever section the update is in. If you only have one section then 0 is right.
if (diff.type == .added) {
// If a document has been added we need to insert a row for it…
// First we filter the results from above to find the task connected to the document ID.
// We use results here because the document doesn't exist in tasks yet.
let filteredResults = results.filter { $0.eventUID == diff.document.documentID }
// Do some saftey checks on the filtered results
if filteredResults.isEmpty {
// Deal with the fact that there is a document that doesn't have a companion task in results. This shouldn't happen, but who knows…
}
if filteredResults.count > 1 {
// Deal with the fact that either the document IDs aren't terribly unique or that a task was added more than once for the saem document
}
let row = results.index(of: filteredResults[0])
let indexPath = IndexPath(row: row, section: section)
// Tell the table view to insert the row
self.tableView.insertRows(at: [indexPath], with: .fade)
} // if added
if (diff.type == .modified) {
// For modifications we need to get the index out of tasks so the index path matches the current path not the one it will have after the updates.
let filteredTasks = self.tasks.filter { $0.eventUID == diff.document.documentID }
// Do some saftey checks on the filtered results
if filteredTasks.isEmpty {
// Deal with the fact that there is a document that doesn't have a companion task in results. This shouldn't happen, but who knows…
}
if filteredTasks.count > 1 {
// Deal with the fact that either the document IDs aren't terribly unique or that a task was added more than once for the saem document
}
let row = self.tasks.index(of: filteredTasks[0])
let indexPath = IndexPath(row: row, section: section)
// Tell the table view to update the row
self.tableView.reloadRows(at: [indexPath], with: .fade)
}
if (diff.type == .removed) {
print("Document removed: \(diff.document.data())")
// For deleted documents we need to use tasks since it doesn't appear in results
let filteredTasks = self.tasks.filter { $0.eventUID == diff.document.documentID }
// Do some saftey checks on the filtered results
if filteredTasks.isEmpty {
// Deal with the fact that there is a document that doesn't have a companion task in results. This shouldn't happen, but who knows…
}
if filteredTasks.count > 1 {
// Deal with the fact that either the document IDs aren't terribly unique or that a task was added more than once for the saem document
}
let row = self.tasks.index(of: filteredTasks[0])
let indexPath = IndexPath(row: row, section: section)
// ** Notice that the above few lines are very similiar in all three cases. The only thing that varies is our use results or self.tasks. You can refactor this out into its own method that takes the array to be filtered and the documentID you are looking for. It could then return either the the row number by itself or the whole index path (returning just the row would be more flexible).
// Tell the table view to remove the row
self.tableView.deleteRows(at: [indexPath], with: .fade)
} // if removed
} // forEach
// Sync tasks and documents with the new info
self.tasks = results
self.documents = snapshot.documents
// Tell the table view you are done with the updates so It can make all the changes
self.tableView.endUpdates()
} // listener
Inside of your change listener all you really need to do is save the indexes of the corresponding changes, save your model objects, and then trigger your table view updates.
let insertions = snapshot.documentChanges.compactMap {
return $0.type == .added ? IndexPath(row: Int($0.newIndex), section: 0) : nil
}
let modifications = snapshot.documentChanges.compactMap {
return $0.type == .modified ? IndexPath(row: Int($0.newIndex), section: 0) : nil
}
let deletions = snapshot.documentChanges.compactMap {
return $0.type == .removed ? IndexPath(row: Int($0.oldIndex), section: 0) : nil
}
self.userDocuments = snapshot.documents
self.tableView.beginUpdates()
self.tableView.insertRows(at: insertions, with: .automatic)
self.tableView.reloadRows(at: modifications, with: .automatic)
self.tableView.deleteRows(at: deletions, with: .automatic)
self.tableView.endUpdates()
There are more efficient ways of mapping the changes to IndexPaths but this was the clearest way to write it.

How do I change data model?

I'm making an social media apps.
user
- displayname
- username
- profileImg
- password
- email
comments
- username
- comment
- to
friends
- follower
- following
hashtags
- hashtag
- to
- by
- comment
likes
- to
- by
posts
- postImg
- username
- title
- uuid
My question is when USERS post the image with title text then
I want retrieve username, profileImg, title, comment, commentby, postImg, count of likes
My approach is redesign the posts db
posts
- postImg
- username
- title
- uuid
- comment
- commentby
- profileImg
- count of likes
But I think it is poor design of db.
func loadPosts() {
//STEP 1. Find posts related to people who we are following
let followQuery = PFQuery(className: “friends")
followQuery.whereKey(“following", equalTo: PFUser.current()!.username!)
followQuery.findObjectsInBackground (block: { (objects:[PFObject]?, error:Error?) -> Void in
if error == nil {
//clean up
self.followArray.removeAll(keepingCapacity: false)
//Appending where people following..
//find related objects
for object in objects! {
self.followArray.append(object.object(forKey: “following") as! String)
}
//append current user to see own posts in feed
self.followArray.append(PFUser.current()!.username!)
//STEP 2. Find posts made by people appended to followArray
let query = PFQuery(className: "posts")
query.whereKey("username", containedIn: self.followArray)
query.limit = self.page
query.addDescendingOrder("createdAt")
query.findObjectsInBackground(block: { (objects:[PFObject]?, error:Error?) -> Void in
if error == nil {
//clean up
self.usernameArray.removeAll(keepingCapacity: false)
// self.profileArray.removeAll(keepCapacity: false)
self.dateArray.removeAll(keepingCapacity: false)
self.postArray.removeAll(keepingCapacity: false)
self.descriptionArray.removeAll(keepingCapacity: false)
self.uuidArray.removeAll(keepingCapacity: false)
self.commentsArray.removeAll(keepingCapacity: false)
self.commentsByArray.removeAll(keepingCapacity: false)
//find related objects
for object in objects! {
self.usernameArray.append(object.object(forKey: "username") as! String)
// self.profileArray.append(object.objectForKey("profileImg") as! PFFile)
self.dateArray.append(object.createdAt)
self.postArray.append(object.object(forKey: "postImg") as! PFFile)
self.descriptionArray.append(object.object(forKey: "title") as! String)
self.uuidArray.append(object.object(forKey: "uuid") as! String)
//set Comments
let comment = object.object(forKey: "comment") as! String
let by = object.object(forKey: "commentby") as! String
let commentString = " " + comment
self.commentsByArray.append(by)
self.commentsArray.append(commentString)
}
//reload tableView & end spinning of refresher
self.tableView.reloadData()
self.refresher.endRefreshing()
} else {
print(error!.localizedDescription)
}
})
} else {
print(error!.localizedDescription)
}
})
}
defined cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//define cell
let cell = tableView.dequeueReusableCell(withIdentifier: "ShopDetailCell", for: indexPath) as! ShopDetailCell
cell.userNameLabel.text = usernameArray[(indexPath as NSIndexPath).row - 1]
cell.userNameLabel.sizeToFit()
cell.uuidLabel.text = uuidArray[(indexPath as NSIndexPath).row - 1]
cell.descriptionLabel.text = descriptionArray[indexPath.row - 1]
cell.descriptionLabel.sizeToFit()
cell.commentLabel.sizeToFit()
//Load ProfileImage
let profileImgQuery = PFQuery(className: "_User")
profileImgQuery.whereKey("username", equalTo: usernameArray[(indexPath as NSIndexPath).row - 1])
profileImgQuery.findObjectsInBackground(block: {(objects:[PFObject]?, error:Error?) -> Void in
if error == nil {
//shown wrong user
if objects!.isEmpty {
print("Wrong User")
}
//find related to user information
for object in objects! {
//Set Image
let profilePictureObject = object.object(forKey: "profileImg") as? PFFile
profilePictureObject?.getDataInBackground { (imageData:Data?, error:Error?) -> Void in
if(imageData != nil)
{
let profileURL : URL = URL(string: profilePictureObject!.url!)!
cell.userImg.sd_setImage(with: profileURL, placeholderImage: UIImage(named: "holderImg"))
}
}
}
} else {
print(error?.localizedDescription)
}
})
//Clip to circle
cell.userImg.layoutIfNeeded()
cell.userImg.layer.cornerRadius = cell.userImg.frame.size.width/2
cell.userImg.clipsToBounds = true
// place post picture using the sdwebimage
let postURL : URL = URL(string: postArray[(indexPath as NSIndexPath).row - 1].url!)!
cell.postImg.sd_setImage(with: postURL, placeholderImage: UIImage(named: "holderImg"))
//Calculate post date
let from = dateArray[(indexPath as NSIndexPath).row - 1]
let now = Date()
let components : NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfMonth]
let difference = (Calendar.current as NSCalendar).components(components, from: from!, to: now, options: [])
// logic what to show : Seconds, minutes, hours, days, or weeks
if difference.second! <= 0 {
cell.dateLabel.text = "NOW"
}
if difference.second! > 0 && difference.minute! == 0 {
cell.dateLabel.text = "\(difference.second!) SEC AGO"
}
if difference.minute! > 0 && difference.hour! == 0 {
cell.dateLabel.text = "\(difference.minute!) MIN AGO"
}
if difference.hour! > 0 && difference.day! == 0 {
cell.dateLabel.text = "\(difference.hour!) HR AGO"
}
if difference.day! > 0 && difference.weekOfMonth! == 0 {
cell.dateLabel.text = "\(difference.day!) DAY AGO"
}
if difference.weekOfMonth! > 0 {
cell.dateLabel.text = "\(difference.weekOfMonth!) WEEK AGO"
}
cell.dateLabel.sizeToFit()
//Set Text Label
if cell.descriptionLabel.text!.isEmpty == true || cell.descriptionLabel.text == " "{
if cell.commentLabel.text!.isEmpty == true || cell.commentLabel.text == " "{
cell.dateTop.constant = 7
}else {
cell.dateTop.constant = cell.commentTop.constant + cell.commentLabel.frame.height + 8
}
}else {
if cell.commentLabel.text!.isEmpty == true || cell.commentLabel.text == " "{
cell.dateTop.constant = cell.descriptionTop.constant + cell.descriptionLabel.frame.height + 8
}else {
cell.commentTop.constant = cell.descriptionTop.constant + cell.descriptionLabel.frame.height + 8
cell.dateTop.constant = cell.commentTop.constant + cell.commentLabel.frame.height + 8
}
}
// manipulate like button depending on did user like it or not
let didLike = PFQuery(className: "likes")
didLike.whereKey("by", equalTo: PFUser.current()!.username!)
didLike.whereKey("to", equalTo: cell.uuidLabel.text!)
didLike.countObjectsInBackground(block: {(count:Int32, error:Error?) -> Void in
//if no any likes are found, else found likes
if count==0 {
cell.likeBtn.setTitle("unlike", for: UIControlState())
cell.likeBtn.setImage(UIImage(named:"heartBtn"), for: UIControlState())
}else{
cell.likeBtn.setTitle("like", for: UIControlState())
cell.likeBtn.setImage(UIImage(named: "heartTapBtn"), for: UIControlState())
}
})
//count total likes of shown post
let countLikes = PFQuery(className: "likes")
countLikes.whereKey("to", equalTo: cell.uuidLabel.text!)
countLikes.countObjectsInBackground(block: {(count:Int32, error:Error?) -> Void in
cell.likesLabel.text="\(count) likes"
})
cell.userNameLabel.layer.setValue(indexPath, forKey: "index")
cell.commentBtn.layer.setValue(indexPath, forKey: "index")
cell.moreBtn.layer.setValue(indexPath, forKey: "index")
return cell
}
Could you anyone advising me?
I had read this tutorial "https://parse.com/tutorials/anypic" but I can't decided which data model is better for me
I wish to uses join or pointer method.
You can save the currentUser as a pointer in your Posts class whenever a user makes a post.
note: I will demonstrate in Objective-C but it's very easy for you to translate into Swift. But if you have trouble reading objc code, I will edit my answer to Swift version.
func post { //make a post
var post = PFObject(className:"Posts")
post["user"] = PFUser.current()//save the current user as a pointer pointing to the User class. You can add a column of type pointer in your parse dashboard inside your Posts class.
//set up other attributes here...
post.saveInBackground()
}
Then when we do the query, we can use includeKey to include the user pointer.
let query = PFQuery(className: "posts")
query.whereKey("username", containedIn: self.followArray)
query.includeKey("user")// THIS IS IMPORTANT
query.limit = self.page
query.addDescendingOrder("createdAt")
query.findObjectsInBackground(block: { (objects:[PFObject]?, error:Error?) -> Void in
if !error {
//we can access the user pointer by doing:
for object in objects {
var user = object["user"]
var username = user.username
var profileImage = user["profileImg"] //a PFFile
//...
}
}
Besides, you can always use a PFQueryTableViewController to load the objects for you, so you don't need to store the query results manually.

Set the KILabel or ActiveLabel's height to 0 when it has no string. But Original UILabel is working fine

This question is related the UITableView and UILabel's height.
Hi I'm using the auto layout to set the dynamic Height in rows.
This is my layout in the table's cell.
Description Label
LikeCountLabel is blue color "1"
Comment Label
Time table is grey "Label"
Time Label
I also checked UILabel's property
import UIKit
import Parse
class ShopDetailCell: UITableViewCell {
#IBOutlet weak var commentBtn: UIButton!
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var profileImg: UIImageView!
#IBOutlet weak var postImg: UIImageView!
#IBOutlet weak var moreBtn: UIButton!
#IBOutlet weak var userNameBtn: UIButton!
#IBOutlet weak var uuidLabel: UILabel!
#IBOutlet weak var descriptionLabel: KILabel!
#IBOutlet weak var likesLabel: UILabel!
#IBOutlet weak var commentLabel: KILabel!
#IBOutlet weak var dateLabel: UILabel!
}
My TableView Controller
import UIKit
import Parse
import SDWebImage
class FeedVC: UITableViewController {
//UI Objects
#IBOutlet weak var indicator: UIActivityIndicatorView!
var refresher=UIRefreshControl()
//arrays to hold server data
var profileArray = [PFFile]()
var usernameArray = [String]()
var dateArray = [NSDate?]()
var postArray = [PFFile]()
var uuidArray = [String]()
var descriptionArray = [String]()
var commentsArray = [String]()
var isLoadedView:Bool = false
var sellingArray = [Bool]()
var followArray = [String]()
//advertise
var advArray = [PFFile]()
//page size
var page : Int = 10
//Default func
override func viewDidLoad() {
super.viewDidLoad()
//background color
tableView?.backgroundColor = UIColor(red: 0.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1)
//automatic row height
tableView.estimatedRowHeight = 450
tableView.rowHeight = UITableViewAutomaticDimension
//pull to refresh
refresher.addTarget(self, action: #selector(FeedVC.loadPosts), forControlEvents: UIControlEvents.ValueChanged)
tableView.addSubview(refresher)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(FeedVC.refresh), name: "liked", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(FeedVC.uploaded(_:)), name: "Uploaded", object: nil)
self.navigationController?.setNavigationBarHidden(true, animated: true)
//calling function to load posts
loadPosts()
//receive notification from UploadViewController
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(FeedVC.scrollToFirstRow(_:)), name: "scrollToTop", object: nil)
}
func scrollToFirstRow(notification:NSNotification) {
if isLoadedView == true {
print("scrollToTop!!!!!")
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}
}
// reloading func with posts after received notification
func uploaded(notification:NSNotification){
print("receicev")
loadPosts()
}
//refresh function
func refresh(){
self.tableView.reloadData()
}
// load posts
func loadPosts() {
//STEP 1. Find posts related to people who we are following
let followQuery = PFQuery(className: "fans")
followQuery.whereKey("myfans", equalTo: PFUser.currentUser()!.username!)
followQuery.findObjectsInBackgroundWithBlock ({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
//clean up
self.followArray.removeAll(keepCapacity: false)
//Appending where people following..
//find related objects
for object in objects! {
self.followArray.append(object.objectForKey("fan") as! String)
}
//append current user to see own posts in feed
self.followArray.append(PFUser.currentUser()!.username!)
//STEP 2. Find posts made by people appended to followArray
let query = PFQuery(className: "posts")
query.whereKey("username", containedIn: self.followArray)
query.limit = self.page
query.addDescendingOrder("createdAt")
query.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
//clean up
self.usernameArray.removeAll(keepCapacity: false)
self.profileArray.removeAll(keepCapacity: false)
self.dateArray.removeAll(keepCapacity: false)
self.postArray.removeAll(keepCapacity: false)
self.descriptionArray.removeAll(keepCapacity: false)
self.uuidArray.removeAll(keepCapacity: false)
//find related objects
for object in objects! {
self.usernameArray.append(object.objectForKey("username") as! String)
self.profileArray.append(object.objectForKey("profileImg") as! PFFile)
self.dateArray.append(object.createdAt)
self.postArray.append(object.objectForKey("postImg") as! PFFile)
self.descriptionArray.append(object.objectForKey("title") as! String)
self.uuidArray.append(object.objectForKey("uuid") as! String)
}
//reload tableView & end spinning of refresher
self.tableView.reloadData()
self.refresher.endRefreshing()
} else {
print(error!.localizedDescription)
}
})
} else {
print(error!.localizedDescription)
}
})
}
//scrolled down
override func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y >= scrollView.contentSize.height - self.view.frame.size.height * 2 {
loadMore()
}
}
// pagination
func loadMore(){
//if posts on the server are more than shown
if page <= uuidArray.count {
//start animating indicator
indicator.startAnimating()
//increase page size to load + 10 posts
page = page + 10
//STEP 1. Find posts related to people who we are following
let followQuery = PFQuery(className: "fans")
followQuery.whereKey("myfans", equalTo: PFUser.currentUser()!.username!)
followQuery.findObjectsInBackgroundWithBlock ({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
//clean up
self.followArray.removeAll(keepCapacity: false)
//Appending where people following..
//find related objects
for object in objects! {
self.followArray.append(object.objectForKey("fan") as! String)
}
//append current user to see own posts in feed
self.followArray.append(PFUser.currentUser()!.username!)
//STEP 2. Find posts made by people appended to followArray
let query = PFQuery(className: "posts")
query.whereKey("username", containedIn: self.followArray)
query.limit = self.page
query.addDescendingOrder("createdAt")
query.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
//clean up
self.usernameArray.removeAll(keepCapacity: false)
self.profileArray.removeAll(keepCapacity: false)
self.dateArray.removeAll(keepCapacity: false)
self.postArray.removeAll(keepCapacity: false)
self.descriptionArray.removeAll(keepCapacity: false)
self.uuidArray.removeAll(keepCapacity: false)
//find related objects
for object in objects! {
self.usernameArray.append(object.objectForKey("username") as! String)
self.profileArray.append(object.objectForKey("profileImg") as! PFFile)
self.dateArray.append(object.createdAt)
self.postArray.append(object.objectForKey("postImg") as! PFFile)
self.descriptionArray.append(object.objectForKey("title") as! String)
self.uuidArray.append(object.objectForKey("uuid") as! String)
}
//reload tableView stop indicator animation
self.tableView.reloadData()
self.indicator.stopAnimating()
} else {
print(error!.localizedDescription)
}
})
} else {
print(error!.localizedDescription)
}
})
}
}
//number of cell
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return uuidArray.count + 1
}
// cell config
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// 가장 첫 페이지는 광고영역..
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("AdvertiseCell", forIndexPath: indexPath) as! AdvertiseCell
return cell
}
//define cell
let cell = tableView.dequeueReusableCellWithIdentifier("ShopDetailCell", forIndexPath: indexPath) as! ShopDetailCell
cell.userNameBtn.setTitle(usernameArray[indexPath.row - 1], forState: UIControlState.Normal)
cell.userNameBtn.sizeToFit()
cell.uuidLabel.text = uuidArray[indexPath.row - 1]
cell.descriptionLabel.text = descriptionArray[indexPath.row - 1]
cell.descriptionLabel.sizeToFit()
// manipulate like button depending on did user like it or not
let isComments = PFQuery(className: "comments")
isComments.whereKey("to", equalTo: cell.uuidLabel.text!)
isComments.limit = 1
isComments.addAscendingOrder("createdAt")
isComments.countObjectsInBackgroundWithBlock({(count:Int32, error:NSError?) -> Void in
//if no any likes are found, else found likes
if count==0 {
cell.commentLabel.text = nil
cell.commentLabel.sizeToFit()
// cell.commentHeight.constant = 0
}else{
//STEP 2. Request last (page size 15) comments
let query = PFQuery(className: "comments")
query.whereKey("to", equalTo: cell.uuidLabel.text!)
query.limit = 1
query.addDescendingOrder("createdAt")
query.findObjectsInBackgroundWithBlock({(objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
//find related object
for object in objects!{
let comment = object.objectForKey("comment") as! String
let by = object.objectForKey("username") as! String
let commentString = by + " " + comment
let boldString = NSMutableAttributedString(string: commentString)
boldString.addAttribute(NSFontAttributeName, value: UIFont(name: "SFUIText-Bold", size: 14)!, range: NSRange(0...by.characters.count))
cell.commentLabel.attributedText = boldString
cell.commentLabel.sizeToFit()
// self.tableView?.reloadData()
}
}else {
print(error?.localizedDescription)
}
})
}
})
//STEP 1. Load data of guest
let profileImgQuery = PFQuery(className: "_User")
profileImgQuery.whereKey("username", equalTo: usernameArray[indexPath.row - 1])
profileImgQuery.findObjectsInBackgroundWithBlock({(objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
//shown wrong user
if objects!.isEmpty {
print("Wrong User")
}
//find related to user information
for object in objects! {
//Set Image
let profilePictureObject = object.objectForKey("profileImg") as? PFFile
profilePictureObject?.getDataInBackgroundWithBlock { (imageData:NSData?, error:NSError?) -> Void in
if(imageData != nil)
{
let profileURL : NSURL = NSURL(string: profilePictureObject!.url!)!
cell.profileImg.sd_setImageWithURL(profileURL, placeholderImage: UIImage(named: "holderImg"))
}
}
}
} else {
print(error?.localizedDescription)
}
})
// place post picture using the sdwebimage
let postURL : NSURL = NSURL(string: postArray[indexPath.row - 1].url!)!
cell.postImg.sd_setImageWithURL(postURL, placeholderImage: UIImage(named: "holderImg"))
//Calculate post date
let from = dateArray[indexPath.row - 1]
let now = NSDate()
let components : NSCalendarUnit = [.Second, .Minute, .Hour, .Day, .WeekOfMonth]
let difference = NSCalendar.currentCalendar().components(components, fromDate: from!, toDate: now, options: [])
// logic what to show : Seconds, minutes, hours, days, or weeks
if difference.second <= 0 {
cell.dateLabel.text = "NOW"
}
if difference.second > 0 && difference.minute == 0 {
cell.dateLabel.text = "\(difference.second) SEC AGO"
}
if difference.minute > 0 && difference.hour == 0 {
cell.dateLabel.text = "\(difference.minute) MIN AGO"
}
if difference.hour > 0 && difference.day == 0 {
cell.dateLabel.text = "\(difference.hour) HR AGO"
}
if difference.day > 0 && difference.weekOfMonth == 0 {
cell.dateLabel.text = "\(difference.day) DAY AGO"
}
if difference.weekOfMonth > 0 {
cell.dateLabel.text = "\(difference.weekOfMonth) WEEK AGO"
}
//
//
// manipulate like button depending on did user like it or not
let didLike = PFQuery(className: "likes")
didLike.whereKey("by", equalTo: PFUser.currentUser()!.username!)
didLike.whereKey("to", equalTo: cell.uuidLabel.text!)
didLike.countObjectsInBackgroundWithBlock({(count:Int32, error:NSError?) -> Void in
//if no any likes are found, else found likes
if count==0 {
cell.likeBtn.setTitle("unlike", forState: .Normal)
cell.likeBtn.setBackgroundImage(UIImage(named:"heartBtn"), forState: .Normal)
}else{
cell.likeBtn.setTitle("like", forState: .Normal)
cell.likeBtn.setBackgroundImage(UIImage(named: "heartTapBtn"), forState: .Normal)
}
})
//count total likes of shown post
let countLikes = PFQuery(className: "likes")
countLikes.whereKey("to", equalTo: cell.uuidLabel.text!)
countLikes.countObjectsInBackgroundWithBlock({(count:Int32, error:NSError?) -> Void in
cell.likesLabel.text="\(count) likes"
})
//asign index
cell.userNameBtn.layer.setValue(indexPath, forKey: "index")
cell.commentBtn.layer.setValue(indexPath, forKey: "index")
cell.moreBtn.layer.setValue(indexPath, forKey: "index")
// #mention is tapped
cell.descriptionLabel.userHandleLinkTapHandler = { label, handle, rang in
var mention = handle
mention = String(mention.characters.dropFirst())
if mention.lowercaseString == PFUser.currentUser()?.username {
let home = self.storyboard?.instantiateViewControllerWithIdentifier("MyShopCollectionVC") as! MyShopCollectionVC
self.navigationController?.pushViewController(home, animated: true)
} else {
guestname.append(mention.lowercaseString)
let guest = self.storyboard?.instantiateViewControllerWithIdentifier("GuestShopCollectionVC") as! GuestShopCollectionVC
self.navigationController?.pushViewController(guest, animated: true)
}
}
// #Hashtag is tapped
cell.descriptionLabel.hashtagLinkTapHandler = {label, handle, range in
var mention = handle
mention = String(mention.characters.dropFirst())
hashtag.append(mention.lowercaseString)
let hashvc = self.storyboard?.instantiateViewControllerWithIdentifier("HashTagsCollectionVC") as! HashTagsCollectionVC
self.navigationController?.pushViewController(hashvc, animated: true)
}
// cell.layoutIfNeeded()
return cell
}
////////
#IBAction func userNameBtnTapped(sender: AnyObject) {
//call index of button
let i = sender.layer.valueForKey("index") as! NSIndexPath
//call cell to call further cell data
let cell = tableView.cellForRowAtIndexPath(i) as! ShopDetailCell
//if user tapped on himeself go home, else go guest
if cell.userNameBtn.titleLabel?.text == PFUser.currentUser()?.username {
let home = self.storyboard?.instantiateViewControllerWithIdentifier("MyShopCollectionVC") as! MyShopCollectionVC
self.navigationController?.pushViewController(home, animated: true)
}else {
guestname.append(cell.userNameBtn.titleLabel!.text!)
let guest = self.storyboard?.instantiateViewControllerWithIdentifier("GuestShopCollectionVC") as! GuestShopCollectionVC
self.navigationController?.pushViewController(guest, animated: true)
}
}
#IBAction func commentBtnTapped(sender: AnyObject) {
print("commentTapped")
// call index of button
let i = sender.layer.valueForKey("index") as! NSIndexPath
// call cell to call further cell data
let cell = tableView.cellForRowAtIndexPath(i) as! ShopDetailCell
//send related data to global variables
commentUUID.append(cell.uuidLabel.text!)
// commentOwner.append(cell.userNameLabel.titleLabel!.text!)
commentOwner.append(cell.userNameBtn.titleLabel!.text!)
let comment = self.storyboard?.instantiateViewControllerWithIdentifier("CommentVC") as! CommentVC
self.navigationController?.pushViewController(comment, animated: true)
}
#IBAction func moreBtnTapped(sender: AnyObject) {
let i = sender.layer.valueForKey("index") as! NSIndexPath
//Call cell to call further cell date
let cell = tableView.cellForRowAtIndexPath(i) as! ShopDetailCell
//Delete Action
let delete = UIAlertAction(title: "Delete", style: .Default) { (UIAlertAction) -> Void in
//STEP 1. Delete row from tablevIEW
self.usernameArray.removeAtIndex(i.row)
self.profileArray.removeAtIndex(i.row)
self.dateArray.removeAtIndex(i.row)
self.postArray.removeAtIndex(i.row)
self.descriptionArray.removeAtIndex(i.row)
self.uuidArray.removeAtIndex(i.row)
//STEP 2. Delete post from server
let postQuery = PFQuery(className: "posts")
postQuery.whereKey("uuid", equalTo: cell.uuidLabel.text!)
postQuery.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
for object in objects! {
object.deleteInBackgroundWithBlock({ (success:Bool, error:NSError?) -> Void in
if success {
//send notification to rootViewController to update shown posts
NSNotificationCenter.defaultCenter().postNotificationName("uploaded", object: nil)
//push back
self.navigationController?.popViewControllerAnimated(true)
} else {
print(error?.localizedDescription)
}
})
}
} else {
print(error?.localizedDescription)
}
})
//STEP 2. Delete likes of post from server
let likeQuery = PFQuery(className: "likes")
likeQuery.whereKey("to", equalTo: cell.uuidLabel.text!)
likeQuery.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
for object in objects! {
object.deleteEventually()
}
}
})
//STEP 3. Delete comments of post from server
let commentQuery = PFQuery(className: "comments")
commentQuery.whereKey("to", equalTo: cell.uuidLabel.text!)
commentQuery.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
for object in objects! {
object.deleteEventually()
}
}
})
//STEP 4. Delete hashtags of post from server
let hashtagQuery = PFQuery(className: "hashtags")
hashtagQuery.whereKey("to", equalTo: cell.uuidLabel.text!)
hashtagQuery.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
for object in objects! {
object.deleteEventually()
}
}
})
}
//Complain Action
let complain = UIAlertAction(title: "Complain", style: .Default) { (UIAlertAction) -> Void in
//send complain to server
let complainObj = PFObject(className: "complain")
complainObj["by"] = PFUser.currentUser()?.username
complainObj["to"] = cell.uuidLabel.text
complainObj["owner"] = cell.userNameBtn.titleLabel?.text
complainObj.saveInBackgroundWithBlock({ (success:Bool, error:NSError?) -> Void in
if success {
self.alert("Complain has been made successfully", message: "Thank You! We will consider your complain")
}else {
self.alert("ERROR", message: error!.localizedDescription)
}
})
}
//Cancel ACTION
let cancel = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
//create menu controller
let menu = UIAlertController(title: "Menu", message: nil, preferredStyle: .ActionSheet)
//if post belongs to user
if cell.userNameBtn.titleLabel?.text == PFUser.currentUser()?.username {
menu.addAction(delete)
menu.addAction(cancel)
} else {
menu.addAction(complain)
menu.addAction(cancel)
}
//show menu
self.presentViewController(menu, animated: true, completion: nil)
}
//alert action
func alert(title: String, message:String){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let ok = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alert.addAction(ok)
presentViewController(alert, animated: true, completion: nil)
}
}
Ok My problem is ...If UILabel's string is nil or "" then It's height should be set to zero. Also Table cell's height is smaller than original height.
UILabel has no strings but It still has height.
Seems like this is the issue in the KILabel library, and the solution is (link) to create custom label class, inherit from KILabel and override this method:
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines {
if (self.text.length == 0) {
return CGRectZero;
}
return [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
}
Declare your UILabel height constraint like this:-
#IBOutlet weak var likeLabelHeightConstraint: NSLayoutConstraint!
And set your height like this
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height : CGFloat = 50 //Default cell Height
if likeLabel.text == nil || likeLabel.text == ""{
height = height - likeLabelHeightConstraint.constant
likeLabelHeightConstraint.constant = 0
}else{
//Set default scenario
}
return height
}
If you are retrieving data asynchronously then make sure you reload your data once your DataSource is updated....