I'm having an issue with the animations in my TableViewCells. Whenever someone crosses of an item on their list, a progress bar animates from 0.0 to 1.0 in 4 seconds:
func startAnimation() {
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
if items![indexRow!].checked {
self.delegate?.changeButton(state: false, indexSection: indexSection!, indexRow: indexRow!, itemID: itemID!)
self.progressBar.setProgress(0.0, animated: false)
self.checkBoxOutlet.setBackgroundImage(#imageLiteral(resourceName: "checkBoxOUTLINE "), for: .normal)
} else {
self.checkBoxOutlet.setBackgroundImage(#imageLiteral(resourceName: "checkBoxFILLED "), for: .normal)
self.tempState = true
UIView.animate(withDuration: 4.0, animations: {
self.progressBar.setProgress(1.0, animated: true)
}) { (finished: Bool) in
self.workItem = DispatchWorkItem {
self.delegate?.changeButton(state: true, indexSection: self.indexSection!, indexRow: self.indexRow!, itemID: self.itemID)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3.3, execute: self.workItem!)
}
}
}
This works, and the animation performs nicely. However, whenever multiple items are being checked quickly after each other, the animation stops when the first animation triggered is completed. Here is a screenrecord of the issue.
As you can see, there are 2 major issues:
The animation of the other cells stop abruptly
Cells that aren't supposed to be deleted get deleted.
I suspect that the issues lies in the delegate method that gets triggered, and not the animation here. This is my delegate method (which updates data in Firestore):
func changeButton(state: Bool, indexSection: Int?, indexRow: Int?, itemID: String?) {
if let indexSection = indexSection, let indexRow = indexRow {
sections[indexSection].items[indexRow].checked = state
}
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
if let itemID = itemID {
let itemRef = db.collection(K.FStore.lists).document(currentListID!).collection(K.FStore.sections).document("\(indexSection!)").collection(K.FStore.items).document(itemID)
if sections[indexSection!].items[indexRow!].checked {
itemRef.updateData([
K.Item.isChecked: true,
K.Item.checkedBy: currentUserID!,
K.Item.dateChecked: Date()
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
if let indexSection = indexSection, let indexRow = indexRow {
if self.sections[indexSection].items != nil {
let item = self.sections[indexSection].items[indexRow]
let itemRef = self.db.collection(K.FStore.lists).document(self.currentListID!).collection(K.FStore.sections).document("\(indexSection)").collection(K.FStore.items).document(item.itemID!)
itemRef.getDocument { (document, error) in
if let document = document, document.exists {
// Get the properties of the item
let name = document.data()?[K.Item.name] as? String
let uid = document.data()?[K.Item.uid] as? String
let category = document.data()?[K.Item.categoryNumber] as? Int
let isChecked = document.data()?[K.Item.isChecked] as? Bool
let dateCreated = document.data()?[K.Item.date] as? Date
let dateChecked = document.data()?[K.Item.dateChecked] as? Date
let checkedBy = document.data()?[K.Item.checkedBy] as? String
self.db.collection(K.lists).document(self.currentListID!).collection(K.FStore.sectionsChecked).document("\(category!)").collection(K.FStore.items).addDocument(data: [
K.Item.name: name,
K.Item.isChecked: isChecked,
K.Item.categoryNumber: category,
K.Item.date: dateCreated,
K.Item.dateChecked: dateChecked,
K.Item.checkedBy: checkedBy,
K.Item.uid: uid,
K.Item.dateDeleted: Date()
]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
let cell = self.tableView.cellForRow(at: IndexPath(item: indexRow, section: indexSection)) as? TaskCell
if let cell = cell {
cell.progressBar.setProgress(0.0, animated: false)
}
// If successful, delete the item in the normal collection
itemRef.delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
}
}
}
}
}
}
}
}
} else {
itemRef.updateData([
K.Item.isChecked : false
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
}
Does it have to do with the refreshing of the cell? I'm quite lost at this point and don't know what I'm doing wrong.
If anyone could help me out that'd be absolutely great.
Related
Please look at my TableView with custom Cell here. I fetched the data from Firestore (and sort it by date descendingly) real time and insert it into the meal array locally. When I clicked the 'eat' button and add a new meal, the 'eat' button isn't sorted correctly?
Here is my code for loadMenu() function and eatButtonPressed() function
loadMenu()
func loadMenu() {
let menuRef = db.collection("menu").document()
db.collection("menu").order(by: "date", descending: true).whereField("family_id", isEqualTo: "\(UserDefaults.standard.string(forKey: "family_id")!)")
.addSnapshotListener { querySnapshot, error in
self.menu = []
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
let name = documents.map { $0["name"] ?? [""] }
let family_id = documents.map { $0["family_id"] ?? [0] }
let portions = documents.map { $0["portions"] ?? [""] }
let menu_id = documents.map { $0["menu_id"] ?? [""]}
let isOpened = documents.map { $0["isOpened"] ?? [""]}
if name != nil || name[0] as! String != "" {
for i in 0..<name.count {
self.menu.append(Menu(menu_id: menu_id[i] as! String, name: name[i] as! String, family_id: family_id[i] as! String, portions: portions[i] as! Int, isOpened: isOpened[i] as! Bool))
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
eatButtonPressed()
func eatButtonPressed(cell: TopPartTableViewCell, send: UIButton) {
var likeRef = self.db.collection("like").document("\(Auth.auth().currentUser!.uid)_\(self.menu[send.tag].menu_id)")
self.db.collection("dislike").document("\(Auth.auth().currentUser!.uid)_\(self.menu[send.tag].menu_id)").delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
}
likeRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
self.db.collection("like").document("\(Auth.auth().currentUser!.uid)_\(self.menu[send.tag].menu_id)").delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
}
} else {
likeRef.setData([
"like_id": "\(likeRef.documentID)",
"user_id": "\(Auth.auth().currentUser!.uid)",
"menu_id": "\(self.menu[send.tag].menu_id)"
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
let user_liked = like.contains(where: {$0.menu_id == menu[send.tag].menu_id}) && like.contains(where: {$0.user_id == Auth.auth().currentUser!.uid})
if !user_liked {
send.backgroundColor = UIColor(named: "BrandOrange")
send.tintColor = UIColor.white
cell.dontEatButton.backgroundColor = UIColor.white
cell.dontEatButton.tintColor = UIColor.black
} else {
send.backgroundColor = UIColor.white
send.tintColor = UIColor.black
}
}
Anyone can help me solve this problem?
Thank you.
The above method doesn't seem to be relevant to the question.
You have added a new item and a cell has been added for that item.
If you look at the detailed structure of the cell, I think you can figure out the cause.
If the cell default button state is Eat, it is most likely not initialized properly.
I'm doing my very first IOS app using Cloud Firestore and have to make the same queries to my database repeatedly. I would like to get rid of the duplicate lines of code. This is examples of func where documents ID are duplicated. Also I using other queries as .delete(), .addSnapshotListener(), .setData(). Should I refactor all that queries somehow or leave them because they were used just for one time?
#objc func updateUI() {
inputTranslate.text = ""
inputTranslate.backgroundColor = UIColor.clear
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { [self] (document, error) in
if let document = document, document.exists {
let document = document
let label = document.data()?.keys.randomElement()!
self.someNewWord.text = label
// Fit the label into screen
self.someNewWord.adjustsFontSizeToFitWidth = true
self.checkButton.isHidden = false
self.inputTranslate.isHidden = false
self.deleteBtn.isHidden = false
} else {
self.checkButton.isHidden = true
self.inputTranslate.isHidden = true
self.deleteBtn.isHidden = true
self.someNewWord.adjustsFontSizeToFitWidth = true
self.someNewWord.text = "Add your first word to translate"
updateUI()
}
}
}
#IBAction func checkButton(_ sender: UIButton) {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { (document, error) in
let document = document
let label = self.someNewWord.text!
let currentTranslate = document!.get(label) as? String
let translateField = self.inputTranslate.text!.lowercased().trimmingCharacters(in: .whitespaces)
if translateField == currentTranslate {
self.inputTranslate.backgroundColor = UIColor.green
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
self.inputTranslate.backgroundColor = UIColor.clear
updateUI()}
} else {
self.inputTranslate.backgroundColor = UIColor.red
self.inputTranslate.shakingAndRedBg()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
self.inputTranslate.backgroundColor = UIColor.clear
self.inputTranslate.text = ""
}
}
}
}
func deletCurrentWord () {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { (document, err) in
let document = document
if let err = err {
print("Error getting documents: \(err)")
} else {
let array = document!.data()
let counter = array!.count
if counter == 1 {
// The whole document will deleted together with a last word in list.
let user = Auth.auth().currentUser?.email
self.db.collection(K.FStore.collectionName).document(user!).delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
self.updateUI()
}
}
} else {
// A current word will be deleted
let user = Auth.auth().currentUser?.email
let wordForDelete = self.someNewWord.text!
self.db.collection(K.FStore.collectionName).document(user!).updateData([
wordForDelete: FieldValue.delete()
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
self.updateUI()
}
}
}
}
}
}
Another query example
func loadMessages() {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.addSnapshotListener { (querySnapshot, error) in
self.messages = []
if let e = error {
print(e)
} else {
if let snapshotDocuments = querySnapshot?.data(){
for item in snapshotDocuments {
if let key = item.key as? String, let translate = item.value as? String {
let newMessage = Message(key: key, value: translate)
self.messages.append(newMessage)
}
}
DispatchQueue.main.async {
self.messages.sort(by: {$0.value > $1.value})
self.secondTableView.reloadData()
let indexPath = IndexPath(row: self.messages.count - 1, section: 0)
self.secondTableView.scrollToRow(at: indexPath, at: .top, animated: false)
}
}
}
}
}
}
enum Error {
case invalidUser
case noDocumentFound
}
func fetchDocument(onError: #escaping (Error) -> (), completion: #escaping (FIRQueryDocument) -> ()) {
guard let user = Auth.auth().currentUser?.email else {
onError(.invalidUser)
return
}
db.collection(K.FStore.collectionName).document(user).getDocument { (document, error) in
if let error = error {
onError(.noDocumentFound)
} else {
completion(document)
}
}
}
func updateUI() {
fetchDocument { [weak self] error in
self?.hideShowViews(shouldHide: true, newWordText: nil)
} completion: { [weak self] document in
guard document.exists else {
self?.hideShowViews(shouldHide: true, newWordText: nil)
return
}
self?.hideShowViews(shouldHide: false, newWordText: document.data()?.keys.randomElement())
}
}
private func hideShowViews(shouldHide: Bool, newWordText: String?) {
checkButton.isHidden = shouldHide
inputTranslate.isHidden = shouldHide
deleteBtn.isHidden = shouldHide
someNewWord.adjustsFontSizeToFitWidth = true
someNewWord.text = newWordText ?? "Add your first word to translate"
}
The updateUI method can easily be refactored using a simple guard statement and then taking out the common code into a separate function. I also used [weak self] so that no memory leaks or retain cycles occur.
Now, you can follow the similar approach for rest of the methods.
Use guard let instead of if let to avoid nesting.
Use [weak self] for async calls to avoid memory leaks.
Take out the common code into a separate method and use a Bool flag to hide/show views.
Update for step 3:
You can create methods similar to async APIs for getDocument() or delete() etc and on completion you can update UI or perform any action. You can also create a separate class and move the fetchDocument() and other similar methods in there and use them.
I am working on a similar feature to 'liking/unliking a post'.
I have an MVVM architecture as;
struct MyStructModel {
var isLiked: Bool? = false
}
class MyStructView {
var isLiked: Bool
init(myStructModel: MyStructModel) {
self.isLiked = myStructModel.isLiked ?? false
}
}
I successfully get the value of whether the post is liked or not here;
func isPostLiked(documentID: String, completion: #escaping (Bool) -> Void) {
guard let authID = auth.id else { return }
let query = reference(to: .users).document(authID).collection("liked").document(documentID)
query.getDocument { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let data = snapshot?.data() else { return }
if let value = data["isLiked"] as? Bool {
completion(value)
} else {
completion(false)
}
}
}
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let snapshotDocuments = snapshot?.documents else { return }
for document in snapshotDocuments {
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
// isLiked is nil here...
self.isPostLiked(documentID: post.documentID!) { (isLiked) in
post.isLiked = isLiked
print("MODEL SAYS: \(post.isLiked!)")
// isLiked is correct value here...
}
posts.append(post)
}
completion(posts)
}
}
}
However, when it gets to my cell the value is still nil.
Adding Cell Code:
var post: MyStructView? {
didSet {
guard let post = post else { return }
print(post.isLiked!)
}
}
Your isLiked property is likely nil in your cells because the retrieveReviews function doesn't wait for the isPostLiked function to complete before completing itself.
You could easily solve this issue by using DispatchGroups. This would allow you to make sure all of your Posts have their isLiked value properly set before being inserted in the array, and then simply use the DispatchGroup's notify block to return all the loaded posts via the completion handler:
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { [weak self] (snapshot, error) in
guard let self = self else { return }
if error != nil {
return
}
guard let documents = snapshot?.documents else { return }
let dispatchGroup = DispatchGroup()
for document in documents {
dispatchGroup.enter()
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
self.isPostLiked(documentID: post.documentID!) { isLiked in
post.isLiked = isLiked
posts.append(post)
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
completion(posts)
}
}
}
I called data from the web for infinite-scrolling and "push to refresh", now infinite-scrolling work well, but "Pull to refresh" is not work because I do not know what kind of code I have to write in refreshAction func.
NetworkRequestAPI Request
import Foundation
class NetworkRequestAPI {
static func getPropductListByCategory(productId : Int, pageNo : Int , completion: #escaping ([Product]? , Error?) -> ()){
let url = URL(string: Configuration.BASE_URL+"/product-by/type?product_type_id="+String(productId)+"&page="+String(pageNo))
var categoryObject = [Product]()
URLSession.shared.dataTask(with:url!) { (urlContent, response, error) in
if error != nil {
}
else {
do {
let json = try JSONSerialization.jsonObject(with: urlContent!) as! [String:Any]
let products = json["products"] as? [String: Any]
// productCount = (json["product_count"] as? Int)!
let items = products?["data"] as? [[String:Any]]
items?.forEach { item in
let oProduct = Product()
oProduct.product_id = item["product_id"] as? Int
oProduct.product_name = item["product_name"] as? String
oProduct.product_image = item["product_image"] as? String
let ratingItem = item["rating_info"] as? [String: AnyObject]
let rating = RatingInfo()
rating.final_rating = ratingItem?["final_rating"] as? String
oProduct.rating_info = rating
categoryObject.append(oProduct)
}
completion(categoryObject, nil)
} catch let error as NSError {
print(error)
completion(nil, error)
}
}
}.resume()
}
}
ListTableView Class
class ListTableView: UITableViewController {
var isInitUILoad = true
var arrProduct = [[Product]]()
var product_id:Int = 0
var pageNo = 1
override func viewDidLoad() {
super.viewDidLoad()
self.initUILoad()
let refreshControl = UIRefreshControl()
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
tableView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(refreshWeatherData(_:)), for: .valueChanged)
}
#objc private func refreshWeatherData(_ sender: Any) {
//
// what codes have to add here
//
self.refreshControl?.endRefreshing()
}
func initUILoad(){
ActivityIndicator.customActivityIndicatory(self.view, startAnimate: true)
NetworkRequestAPI.getPropductListByCategory(productId: product_id, pageNo: pageNo) { (products, error) in
DispatchQueue.main.async(execute: {
if products != nil {
// self.totalItemLabel.text = String(self.product_count) + " products"
self.pageNo += 1
// self.product_count = productCount
//self.arrProduct = products!
self.arrProduct.append(products!)
print(self.arrProduct.count)
// self.tableView?.reloadData()
}else{
print(error.debugDescription)
}
ActivityIndicator.customActivityIndicatory(self.view, startAnimate: false)
// self.totalItemLabel.text = String(self.product_count) + " products"
self.tableView?.reloadData()
self.isInitUILoad = false
})
}
}
func loadMore(complition:#escaping (Bool) -> ()) {
NetworkRequestAPI.getPropductListByCategory(productId: product_id, pageNo: pageNo) { (products, error) in
DispatchQueue.main.async(execute: {
if error != nil{
print(error.debugDescription)
}
if products != nil && products?.count ?? 0 > 0{
// self.totalItemLabel.text = String(self.product_count) + " products"
self.pageNo += 1
// self.product_count = productCount
// self.arrProduct = products!
self.arrProduct.append(products!)
self.tableView?.insertSections([self.arrProduct.count - 1], with: .fade)
// self.tableView?.reloadData()
}else{
print("no product left")
}
complition(true)
})
}
}
}
#objc private func refreshWeatherData(_ sender: Any) {
NetworkRequestAPI.getPropductListByCategory(productId: product_id, pageNo: 1) { (products, error) in
DispatchQueue.main.async(execute: {
if products != nil {
// self.totalItemLabel.text = String(self.product_count) + " products"
self.arrProduct = products!
print(self.arrProduct.count)
}else{
print(error.debugDescription)
}
self.refreshControl?.endRefreshing()
// self.totalItemLabel.text = String(self.product_count) + " products"
self.tableView?.reloadData()
self.isInitUILoad = false
})
}
}
I am using UITableview and it looks similar with the feed of Instagram. The issue I have
I have a like function in each tableviewCell
when tap like button, it needs to update the screen and the screen blinks
In tableview cellForRowAt function, I have a network call to check the like and its number.
Please let me know if there is a way to avoid this blink. Should I avoid network call in this function or is there any other way?
U can see some part of my code below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! StoryReviewTableViewCell
let review = ReviewArray[indexPath.row]
// 프로필 이미지랑 닉네임 설정
if let user = review.creator {
let nickname = user.getProperty("nickname") as! String
cell.profileName.text = nickname
if let profileURL = user.getProperty("profileURL") {
if profileURL is NSNull {
cell.profileImage.image = #imageLiteral(resourceName: "user_profile")
} else {
let url = URL(string: profileURL as! String)
DispatchQueue.main.async {
cell.profileImage.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "imageLoadingHolder"), options: [.transition(.fade(0.2))], progressBlock: nil, completionHandler: nil)
}
}
}
} else {
// 삭제된 유저의 경우
cell.profileName.text = "탈퇴 유저"
cell.profileImage.image = #imageLiteral(resourceName: "user_profile")
}
// 장소 이름
if let store = ReviewArray[indexPath.row].store {
cell.storeName.text = "장소: \(String(describing: store.name!))"
} else {
cell.storeName.text = "가게 이름"
}
// 라이크버튼 설정 - 라이크 모양은 여기서 컨트롤, delegate에서 user 라이크 컨트롤
DispatchQueue.global(qos: .userInteractive).async {
let likeStore = Backendless.sharedInstance().data.of(ReviewLikes.ofClass())
let dataQuery = BackendlessDataQuery()
let objectID = review.objectId!
let userID = UserManager.currentUser()!.objectId!
// print("objectID & userID: \(objectID) & \(userID)")
// 여기서 by가 현재 유저의 objectId이어야 하고, to는 이 리뷰의 objectId이어야 한다
dataQuery.whereClause = "by = '\(userID)' AND to = '\(objectID)'"
DispatchQueue.main.async {
likeStore?.find(dataQuery, response: { (collection) in
let likes = collection?.data as! [ReviewLikes]
// 하트를 안 눌렀을 때
if likes.count == 0 {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}
} else {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}
}
}, error: { (Fault) in
print("라이크 불러오기에서 에러: \(String(describing: Fault?.description))")
})
}
// 좋아요 개수 세기
let countQuery = BackendlessDataQuery()
// to가 story의 objectID와 일치하면 땡
countQuery.whereClause = "to = '\(objectID)'"
let queryOptions = QueryOptions()
queryOptions.pageSize = 1
countQuery.queryOptions = queryOptions
DispatchQueue.global(qos: .userInteractive).async {
let matchingLikes = likeStore?.find(countQuery)
let likeNumbers = matchingLikes?.totalObjects
DispatchQueue.main.async {
if likeNumbers == 0 {
cell.likeLabel.text = "라이크 없음 ㅠ"
} else {
cell.likeLabel.text = "\(String(describing: likeNumbers!))개의 좋아요"
}
}
}
}
// 리뷰 평점 배당
cell.ratingView.value = review.rating as! CGFloat
// 리뷰 바디
cell.reviewBody.text = review.text
// 코멘트 개수 받아오기
DispatchQueue.global(qos: .userInteractive).async {
// 댓글수 찾기
let tempStore = Backendless.sharedInstance().data.of(ReviewComment.ofClass())
let reviewId = review.objectId!
let dataQuery = BackendlessDataQuery()
// 이 리뷰에 달린 댓글 모두 몇 개인지 찾기
dataQuery.whereClause = "to = '\(reviewId)'"
DispatchQueue.main.async {
tempStore?.find(dataQuery, response: { (collection) in
let comments = collection?.data as! [ReviewComment]
cell.replyLabel.text = "댓글 \(comments.count)개"
}, error: { (Fault) in
print("서버에서 댓글 얻어오기 실패: \(String(describing: Fault?.description))")
})
}
}
cell.timeLabel.text = dateFormatter.string(from: review.created! as Date)
return cell
}
U can see the button action here
#IBAction func likeButtonClicked(_ sender: UIButton) {
likeButton.isUserInteractionEnabled = false
// delegate action
delegate?.actionTapped(tag: likeButton.tag)
// image change
if sender.image(for: .normal) == #imageLiteral(resourceName: "like_bw") {
UIView.transition(with: sender, duration: 0.2, options: .transitionCrossDissolve, animations: {
sender.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}, completion: nil)
self.likeButton.isUserInteractionEnabled = true
} else {
UIView.transition(with: sender, duration: 0.2, options: .transitionCrossDissolve, animations: {
sender.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}, completion: nil)
self.likeButton.isUserInteractionEnabled = true
}
}
This is part of my delegate function which reload only for the row
func changeLike(_ row: Int, _ alreadyLike: Bool, completionHandler: #escaping (_ success:Bool) -> Void) {
let selectedReview = ReviewArray[row]
let reviewId = selectedReview.objectId
// 그냥 유저 객체로 비교는 안되고 objectId로 체크를 해야 함
let objectID = Backendless.sharedInstance().userService.currentUser.objectId
let dataStore = Backendless.sharedInstance().data.of(ReviewLikes.ofClass())
// 좋아요 - alreadyLike가 true이면
if !alreadyLike {
// 객체 생성
let like = ReviewLikes()
like.by = objectID! as String
like.to = reviewId
dataStore?.save(like, response: { (response) in
DispatchQueue.main.async {
let indexPath = IndexPath(row: row, section: 0)
self.tableView.reloadRows(at: [indexPath], with: .none)
}
}
let cell = self.tblView.cellForRow(at: IndexPath(row: index, section: 0)) as! UITableViewCell
cell.btnLike.setImage(UIImage(named: (isReviewed! ? IMG_LIKE : IMG_UNLIKE)), for: .normal)
Just update the cell, instead of reloading whole data in the tableview.
Try to call data on button click instead of cellForRowAtIndexpath and use that button which clicked instead of reload data
#IBAction func likeButtonClicked(_ sender: UIButton) {
let buttonPosition:CGPoint = sender.convert(CGPointZero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
let cell = tableView.cellForRow(at: indexPath) as! StoryReviewTableViewCell
DispatchQueue.main.async {
likeStore?.find(dataQuery, response: { (collection) in
let likes = collection?.data as! [ReviewLikes]
// 하트를 안 눌렀을 때
if likes.count == 0 {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}
} else {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}
}
}, error: { (Fault) in
print("라이크 불러오기에서 에러: \(String(describing: Fault?.description))")
})
}