Buggy UI Tableview - ios

I want the view to be like Apple maps UI Tableview when you click on a map annotation, the UI Tableview moves up with the loaded information and it is smooth. I created a UITableView to load data from a Yelp API and Firebase database. While I do have the data loaded in the UI Tableview, there seems to be choppiness in the movement of the tableview. For example, when I first click on a map annotation, the UI Tableview will pop up, but in a random position and then after the Yelp API loads, it will move again to its default position. Another thing is that if I use a swipe gesture before the Yelp API loads, the UI Tableview will move accordingly, but then reset to its original position when the Yelp API data loads, which then I have to redo the swipe gesture.
There are many parts of this tableview, so I will provide a list of pieces of code that I use:
Note: The UI Tableview (locationInfoViews) is configured in the ViewDidLoad
Swiping up/down
func configureGestureRecognizer() {
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture))
swipeUp.direction = .up
addGestureRecognizer(swipeUp)
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture))
swipeDown.direction = .down
addGestureRecognizer(swipeDown)
}
func animateInputView(targetPosition: CGFloat, completion: #escaping(Bool) -> ()) {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {self.frame.origin.y = targetPosition}, completion: completion)
}
// MARK: - Handlers
#objc func handleSwipeGesture(sender: UISwipeGestureRecognizer) {
if sender.direction == .up {
if expansionState == .Disappeared {
animateInputView(targetPosition: self.frame.origin.y - 100) { (_) in
self.expansionState = .NotExpanded
}
}
if expansionState == .NotExpanded {
animateInputView(targetPosition: self.frame.origin.y - 200) { (_) in
self.expansionState = .PartiallyExpanded
}
}
if expansionState == .PartiallyExpanded {
animateInputView(targetPosition: self.frame.origin.y - 250) { (_) in
self.expansionState = .FullyExpanded
}
}
} else {
if expansionState == .FullyExpanded {
animateInputView(targetPosition: self.frame.origin.y + 250) { (_) in
self.expansionState = .PartiallyExpanded
}
}
if expansionState == .PartiallyExpanded {
animateInputView(targetPosition: self.frame.origin.y + 200) { (_) in
self.expansionState = .NotExpanded
}
}
if expansionState == .NotExpanded {
animateInputView(targetPosition: self.frame.origin.y + 100) { (_) in
self.expansionState = .Disappeared
}
}
}
}
When select annotation, information from Yelp and Firebase will be loaded into the location info view and the animation should move up
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
self.locationPosts.removeAll()
self.inLocationInfoMode = true
if view.annotation is MKUserLocation {
print("selected self")
} else {
self.selectedAnnotation = view.annotation as? Annotation
let coordinates = selectedAnnotation.coordinate
let coordinateRegion = MKCoordinateRegion(center: coordinates, latitudinalMeters: 1000, longitudinalMeters: 100)
mapView.setRegion(coordinateRegion, animated: true)
self.locationInfoViews.locationTitle = self.selectedAnnotation.title
// Fetch Location Post
guard let currentUid = Auth.auth().currentUser?.uid else {return}
LOCATION_REF.child(self.selectedAnnotation.title).child(currentUid).observe(.childAdded) { (snapshot) in
let postId = snapshot.key
Database.fetchLocationPost(with: postId) { (locationPost) in
self.locationPosts.append(locationPost)
self.locationPosts.sort { (post1, post2) -> Bool in
return post1.creationDate > post2.creationDate
}
self.locationInfoViews.locationResults = self.locationPosts
// self.locationInfoViews.tableView.reloadData()
// Fetch Location Information
guard let locationName = locationPost.locationName else {return}
guard let locationAddress = locationPost.address else {return}
let locationRef = COORDINATES_REF.child(locationName).child(locationAddress)
locationRef.child("mainTypeOfPlace").observe(.value) { (snapshot) in
guard let mainTypeOfPlace = snapshot.value as? String else {return}
// self.locationInfoViews.typeOfPlace = mainTypeOfPlace
locationRef.child("shortAddress").observe(.value) { (snapshot) in
guard let address1 = snapshot.value as? String else {return}
locationRef.child("city").observe(.value) { (snapshot) in
guard let city = snapshot.value as? String else {return}
locationRef.child("state").observe(.value) { (snapshot) in
guard let state = snapshot.value as? String else {return}
locationRef.child("countryCode").observe(.value) { (snapshot) in
guard let country = snapshot.value as? String else {return}
// fetch Yelp API Data
self.service.request(.match(name: locationName, address1: address1, city: city, state: state, country: country)) {
(result) in
switch result {
case .success(let response):
let businessesResponse = try? JSONDecoder().decode(BusinessesResponse.self, from: response.data)
let firstID = businessesResponse?.businesses.first?.id
self.information.request(.BusinessID(id: firstID ?? "")) {
(result) in
switch result {
case .success(let response):
if let jsonResponse = try? JSONSerialization.jsonObject(with: response.data, options: []) as? [String: Any] {
// print(jsonResponse)
if let categories = jsonResponse["categories"] as? Array<Dictionary<String, AnyObject>> {
var mainCategory = ""
for category in categories {
mainCategory = category["title"] as? String ?? ""
break
}
self.locationInfoViews.typeOfPlace = mainCategory
}
let price = jsonResponse["price"] as? String ?? ""
if let hours = jsonResponse["hours"] as? Array<Dictionary<String, AnyObject>> {
for hour in hours {
let isOpen = hour["is_open_now"] as? Int ?? 0
if isOpen == 1 {
let attributedText = NSMutableAttributedString(string: "open ", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor(rgb: 0x066C19)])
attributedText.append(NSAttributedString(string: " \(price)", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.black]))
self.locationInfoViews.hoursLabel.attributedText = attributedText
} else {
let attributedText = NSMutableAttributedString(string: "closed ", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.red])
attributedText.append(NSAttributedString(string: " \(price)", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.darkGray]))
self.locationInfoViews.hoursLabel.attributedText = attributedText
}
}
}
}
case .failure(let error):
print("Error: \(error)")
}
}
case .failure(let error):
print("Error: \(error)")
}
}
}
}
}
}
}
self.locationInfoViews.tableView.reloadData()
}
}
// enable the goButton
if inLocationInfoMode {
locationInfoViews.goButton.isEnabled = true
locationInfoViews.coordinates = selectedAnnotation.coordinate
}
// the movement of the location info view
if self.locationInfoViews.expansionState == .Disappeared {
self.locationInfoViews.animateInputView(targetPosition: self.locationInfoViews.frame.origin.y - 335) { (_) in
self.locationInfoViews.expansionState = .PartiallyExpanded
}
}
}
}

Try adding a completion block for the fetching, first extract it into a separate function. On tap on the annotation show some loading state, while you load the data. Once you receive the completion block, decide what to do depending on the result. The fetching will be done while the user sees a loading state, and no flickering will occur.

Related

Animation in cell stopping when other cell's animation finishes

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.

retrive firebase info to set button titles

I am trying to retrieve information from firebase to set button titles which allow users to interact with the view in different ways. I am not sure about how to go about this so I will post some code in hopes someone understands.
func fetchJobProgress() {
let workerId = workerUser?.uid
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("workInProgress").child(uid!).queryOrdered(byChild: "fromId").queryEqual(toValue: workerId).observeSingleEvent(of: .value, with: { (snapshot) in
self.confirmButton.backgroundColor = GREEN_Theme
self.confirmButton.addTarget(self, action: #selector(self.handleConfirm), for: .touchUpInside)
}, withCancel: { (err) in
print("attempting to load information")
})
}
Change your function to:
func fetchJobProgress() {
let workerId = workerUser?.uid
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("workInProgress").child(uid!).queryOrdered(byChild: "fromId").queryEqual(toValue: workerId).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else { return }
for (_, valueDic) in dictionary {
guard let dic = valueDic as? [String: Any] else {
continue
}
guard let type = dic["type"] as? Int else {
continue
}
if type == userNotifications.NotificationType.accepted.rawValue {
self.confirmButton.backgroundColor = GREEN_Theme
self.confirmButton.addTarget(self, action: #selector(self.handleConfirm), for: .touchUpInside)
} else if type == userNotifications.NotificationType.inProgress.rawValue {
self.confirmButton.setTitle("In Progress...", for: .normal)
self.confirmButton.backgroundColor = UIColor.lightGray
self.confirmButton.addTarget(self, action: #selector(self.handleComplete), for: .touchUpInside)
} else if type == userNotifications.NotificationType.confirmCompleted.rawValue {
self.confirmButton.setTitle("Completed!", for: .normal)
self.confirmButton.backgroundColor = GREEN_Theme
self.confirmButton.isUserInteractionEnabled = false
}
break
}
}, withCancel: { (err) in
print("attempting to load information")
})
}

push data from UITableView to view controller programmatically

I am trying to push data from a tableView to a View controller. I can successfully transfer some of the data over, but I am still missing some key points. I will try to illustrate my question to the best of my abilities. In my notificationTableView, I have data that is stored such as a userName, userImage, jobName and jobImage. I can succesfully push over the users image and name, however The jobName and JobImage fails to be transferred over as we can see in the Images below.
In this image, we can see the tableView sections that have the userName, userImage, jobName and jobImage.
In the second image, we can see that the usersName, and Image is succesfully pushed. However, the jobImage and name are not transferred.
the code that I use to push over the information is
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let notification = notifications[indexPath.row]
if notification.notificationType != .swipe {
let acceptWorker = jobProgressViewController()
acceptWorker.workerUser = myUser
acceptWorker.workerUser = notification.user
jobProgressView?.myParentViewController = self
let navController = UINavigationController(rootViewController: acceptWorker)
present(navController, animated: true, completion: nil)
} else {
print("something else should go here")
}
and the code that I use to retrieve the information is below. which is my jobProgressViewController
var notification: userNotifications?
var workerUser: User? {
didSet {
let name = workerUser?.name
workerNameLabel.text = name
guard let profileImage = workerUser?.profileImageUrl else { return }
workerImageView.loadImageUsingCacheWithUrlString(profileImage)
if let post = notification?.poster {
jobImageView.loadImageUsingCacheWithUrlString(post.imageUrl1!)
jobLabel.text = post.category
addressLabel.text = post.category
}
}
}
fileprivate func setupView(){
let postUser = workerUser.self
let uid = Auth.auth().currentUser?.uid
let userName = postUser?.name
let posterId = postUser?.uid
let post = notification?.poster
guard let userImage = workerUser?.profileImageUrl else { return }
Database.database().reference().child("users").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else { return }
let user = User(dictionary: dictionary as [String : AnyObject])
let currentUser = MyUser(dictionary: dictionary as [String : AnyObject])
self.posterImageView.image = #imageLiteral(resourceName: "user")
self.posterImageView.loadImageUsingCacheWithUrlString(userImage)
self.userNameLabel.text = userName
self.userNameLabel.font = UIFont.systemFont(ofSize: 30)
self.userNameLabel.textColor = UIColor.black
self.workerImageView.image = #imageLiteral(resourceName: "user")
self.workerImageView.loadImageUsingCacheWithUrlString(currentUser.profileImageUrl!)
self.workerNameLabel.text = currentUser.name
self.workerNameLabel.font = UIFont.systemFont(ofSize: 30)
self.workerNameLabel.textColor = UIColor.black
self.addressLabel.text = postUser?.address
self.addressLabel.font = UIFont.systemFont(ofSize: 30)
self.addressLabel.textColor = UIColor.black
self.jobLabel.text = post?.category
self.jobLabel.font = UIFont.systemFont(ofSize: 30)
self.jobLabel.textColor = UIColor.black
}, withCancel: { (err) in
print("attempting to load information")
})
print("this is your uid \(posterId!)")
}
below is how I populate my notificationCell which shows the users information in my tableView
var jobProgressView: jobProgressViewController? = nil
var delegate: NotificationCellDelegate?
var notification: userNotifications? {
didSet {
guard let user = notification?.user else { return }
guard let profileImageUrl = user.profileImageUrl else { return }
profileImageView.loadImageUsingCacheWithUrlString(profileImageUrl)
configureNotificationLabel()
configureNotificationType()
if let post = notification?.poster {
postImageView.loadImageUsingCacheWithUrlString(post.imageUrl1!)
}
}
}
func configureNotificationLabel() {
guard let notification = self.notification else { return }
guard let user = notification.user else { return }
guard let poster = notification.poster else { return }
guard let username = user.name else { return }
guard let notificationDate = configureNotificationTimeStamp() else { return }
guard let jobName = poster.category else { return }
let notificationMessage = notification.notificationType.description
let attributedText = NSMutableAttributedString(string: username, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string: notificationMessage , attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14), NSAttributedString.Key.foregroundColor: UIColor.black]))
attributedText.append(NSAttributedString(string: jobName, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14), NSAttributedString.Key.foregroundColor: UIColor.black]))
attributedText.append(NSAttributedString(string: " \(notificationDate).", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14), NSAttributedString.Key.foregroundColor: UIColor.gray]))
notificationLabel.attributedText = attributedText
}
if there is anyInformation I may have left out to help with getting an answer please let me know. please and thank you.
As discusstion, you forget set notification for jobProgressViewController
In func didSelectRowAt indexPath add below code:
acceptWorker.notification = notification

Swift Firebase Refreshing TableView causes cells to repeat

I have a tableview refresh action and when used it cause 2 extra duplicate cells to appear. I have been reading over the code and testing what could cause the refresh to create 2 extra duplicate cells. Not sure about this. When I refresh it will create 2 duplicate extra cells and then the table view spinning wheel will keep on turning and not disappear.
override func viewDidLoad() {
super.viewDidLoad()
databaseRef = Database.database().reference()
locationManager.delegate = self
locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
locationManager.stopUpdatingLocation()
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
case .notDetermined, .restricted, .denied:
print("No access")
fetchPosts()
//getAllPostsWithoutLocation(refreshing: false, refreshControl: nil)
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
fetchPostsWithLocation()
//getAllPosts(refreshing: false, refreshControl: nil)
}
} else {
print("Location services are not enabled")
}
// refresh control
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshControlAction(refreshControl:)), for: UIControlEvents.valueChanged)
self.feedTableView.insertSubview(refreshControl, at: 0)
}
#objc func refreshControlAction(refreshControl: UIRefreshControl) {
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
case .notDetermined, .restricted, .denied:
print("No access")
fetchPosts()
//getAllPostsWithoutLocation(refreshing: true, refreshControl: refreshControl)
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
fetchPostsWithLocation()
//getAllPosts(refreshing: true, refreshControl: refreshControl)
}
} else {
print("Location services are not enabled")
}
}
func fetchPostsWithLocation() {
Database.database().reference().child("user_profiles").child((loggedInUser?.uid)!).child("following").observe(.value, with: { snapshot in
if snapshot.exists() {
MBProgressHUD.showAdded(to: self.view, animated: true)
let databaseRef = Database.database().reference()
// retrieves all users from database
databaseRef.child("user_profiles").queryOrderedByKey().observeSingleEvent(of: .value, with: { (usersSnapshot) in
let users = usersSnapshot.value as! [String: AnyObject]
// retrieve user's following list and append it
for (_, value) in users {
print(value)
if let userID = value["uid"] as? String {
if userID == Auth.auth().currentUser?.uid {
print(value)
if let followingUsers = value["following"] as? [String : String] {
for (_,user) in followingUsers {
self.following.append(user)
}
}
// append user's id to see own posts
//self.following.append(Auth.auth().currentUser!.uid)
// retrieve all posts from the database
databaseRef.child("posts").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
// retrieve posts of each follower and user
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String! else {return}
guard let caption = postInfo["caption"] as! String! else {return}
guard let downloadURL = postInfo["download_url"] as! String! else {return}
guard let name = postInfo["businessName"] as! String! else {return}
guard let timestamp = postInfo["timestamp"] as! Double! else {return}
let date = Date(timeIntervalSince1970: timestamp/1000)
guard let address = postInfo["businessStreet"] as! String! else {return}
guard let state = postInfo["businessCity"] as! String! else {return}
guard let postID = postInfo["postID"] as! String! else {return}
let lat = Double(postInfo["businessLatitude"] as! String)
let long = Double(postInfo["businessLongitude"] as! String)
let businessLocation = CLLocation(latitude: lat!, longitude: long!)
let latitude = self.locationManager.location?.coordinate.latitude
let longitude = self.locationManager.location?.coordinate.longitude
let userLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let distanceInMeters: Double = userLocation.distance(from: businessLocation)
let distanceInMiles: Double = distanceInMeters * 0.00062137
let distanceLabelText = String(format: "%.2f miles away", distanceInMiles)
self.feeds.append(Post(uid: uid, caption: caption, downloadURL: downloadURL, name: name, date: date, address: address, state: state, distance: distanceLabelText, postID: postID))
}
self.feeds.sort {$0.date.compare($1.date) == .orderedDescending}
//self.feeds.sort {$0.distance.compare($1.distance) == .orderedAscending}
self.feedTableView.reloadData()
}
}
}
}
MBProgressHUD.hide(for: self.view, animated: true)
}) { (error) in
print(error.localizedDescription)
}
}
}
}
})
} else {
print("Not following anyone")
}
})
}

swift3 uitableView reload makes screen blink

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))")
})
}

Resources