retrive firebase info to set button titles - ios

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

Related

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

Buggy UI Tableview

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.

Firebase Realtime Array count mismatch

I have an iOS swift app using Firebase realtime database. If I use the app normally so far I cannot find any issue. However, I want to anticipate edge cases.
I am trying to stress test my app before I push the update, and one way I am doing it is quickly going back and forth from a VC with a tableView to the next VC which is a detail VC. If I do it several times eventually the tableview will show lots of duplicate data.
I have tested my app by having a tableview open on my simulator and going into my Firebase Console and manually changing a value and instantly on the device the string changes.
So I am confused as to why my tableview would show an incorrect amount of children if it is constantly checking what the value should be.
// MARK: Firebase Methods
func checkIfDataExits() {
DispatchQueue.main.async {
self.cardArray.removeAll()
self.ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.hasChild("cards") {
self.pullAllUsersCards()
} else {
self.tableView.reloadData()
}
})
}
}
func pullAllUsersCards() {
cardArray.removeAll()
let userRef = ref.child("users").child((user?.uid)!).child("cards")
userRef.observe(DataEventType.value, with: { (snapshot) in
for userscard in snapshot.children {
let cardID = (userscard as AnyObject).key as String
let cardRef = self.ref.child("cards").child(cardID)
cardRef.observe(DataEventType.value, with: { (cardSnapShot) in
let cardSnap = cardSnapShot as DataSnapshot
let cardDict = cardSnap.value as! [String: AnyObject]
let cardNickname = cardDict["nickname"]
let cardType = cardDict["type"]
let cardStatus = cardDict["cardStatus"]
self.cardNicknameToTransfer = cardNickname as! String
self.cardtypeToTransfer = cardType as! String
let aCard = CardClass()
aCard.cardID = cardID
aCard.nickname = cardNickname as! String
aCard.type = cardType as! String
aCard.cStatus = cardStatus as! Bool
self.cardArray.append(aCard)
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
})
}
I got help and changed my code drastically, so now it works
func checkIfDataExits() {
self.ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.hasChild("services") {
self.pullCardData()
} else {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
})
}
func pullCardData() {
let cardRef = self.ref.child("cards")
cardRef.observe(DataEventType.value, with: { (snapshot) in
for cards in snapshot.children {
let allCardIDs = (cards as AnyObject).key as String
if allCardIDs == self.cardID {
if let childId = self.cardID {
let thisCardLocation = cardRef.child(childId)
thisCardLocation.observe(DataEventType.value, with: { (snapshot) in
let thisCardDetails = snapshot as DataSnapshot
if let cardDict = thisCardDetails.value as? [String: AnyObject] {
self.selectedCard?.cardID = thisCardDetails.key
self.selectedCard?.nickname = cardDict["nickname"] as? String ?? ""
self.selectedCard?.type = cardDict["type"] as? String ?? ""
self.pullServicesForCard()
}
})
}
}
}
})
}
func pullServicesForCard() {
if let theId = self.cardID {
let thisCardServices = self.ref.child("cards").child(theId).child("services")
thisCardServices.observe(DataEventType.value, with: { (serviceSnap) in
if self.serviceArray.count != Int(serviceSnap.childrenCount) {
self.serviceArray.removeAll()
self.fetchAndAddAllServices(serviceSnap: serviceSnap, index: 0, completion: { (success) in
if success {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
})
}
})
}
}
func fetchAndAddAllServices(serviceSnap: DataSnapshot, index: Int, completion: #escaping (_ success: Bool) -> Void) {
if serviceSnap.hasChildren() {
if index < serviceSnap.children.allObjects.count {
let serviceChild = serviceSnap.children.allObjects[index]
let serviceID = (serviceChild as AnyObject).key as String
let thisServiceLocationInServiceNode = self.ref.child("services").child(serviceID)
thisServiceLocationInServiceNode.observeSingleEvent(of: DataEventType.value, with: { (thisSnap) in
let serv = thisSnap as DataSnapshot
if let serviceDict = serv.value as? [String: AnyObject] {
let aService = ServiceClass(serviceDict: serviceDict)
self.serviceCurrent = serviceDict["serviceStatus"] as? Bool
self.serviceName = serviceDict["serviceName"] as? String ?? ""
self.serviceURL = serviceDict["serviceURL"] as? String ?? ""
self.serviceFixedBool = serviceDict["serviceFixed"] as? Bool
self.serviceFixedAmount = serviceDict["serviceAmount"] as? String ?? ""
self.attentionInt = serviceDict["attentionInt"] as? Int
self.totalArr.append((serviceDict["serviceAmount"] as? String)!)
// self.doubleArray = self.totalArr.flatMap{ Double($0) }
// let arraySum = self.doubleArray.reduce(0, +)
// self.title = self.selectedCard?.nickname ?? ""
// if let titleName = self.selectedCard?.nickname {
// self.title = "\(titleName): \(arraySum)"
// }
aService.serviceID = serviceID
if serviceDict["serviceStatus"] as? Bool == true {
self.selectedCard?.cStatus = true
} else {
self.selectedCard?.cStatus = false
}
if !self.serviceArray.contains(where: { (service) -> Bool in
return service.serviceID == aService.serviceID
}) {
self.serviceArray.append(aService)
self.serviceArray.sort {$1.serviceAttention < $0.serviceAttention}
}
}
self.fetchAndAddAllServices(serviceSnap: serviceSnap, index: index + 1, completion: completion)
})
}
else {
completion(true)
}
}
else {
completion(false)
}
}

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

First time button is pressed it don't work, but it works super the second time

I'm quite new to coding overall, learning swift 3, and I'm trying to integrate a button that "stars" if pressed. So currently it writes to Firebase, the status "true", together with some more data if pressed.
The issue is that it only works the 2 time it is pressed. But after that it works perfectly...
(edit: by "not work" I mean that it don't update the image to "filled 64x64" the first time I press. I just checked in Firebase, and it seems it creates a record, the first time I press, but puts it as favourite=false)
Can some more experienced coders help me understand whats wrong?
This is the code from the "star"-button:
/*
==========================================================================================
//MARK: Start of code that stars your whisky Firebase
==========================================================================================
*/
var isStarred = UserDefaults.standard.bool(forKey: "isStarred")
#IBAction func starButton(_ sender: Any) {
//loads info for query
let currentUserID = self.user?.uid
let realIndex:Int? = detailWhisky?.realIndex
let realIndex2 = "\(realIndex!)"
//executes query
let starRef2 = FIRDatabase.database().reference().child("Starred").child(currentUserID!).child(realIndex2)
//print("booyah")
//changes state of button and loads true/false to Firebase
if isStarred == true {
let image = UIImage(named: "star64x64.png")
(sender as AnyObject).setImage(image, for: UIControlState.normal)
let favourite = ["favourite": false, "realIndex": detailWhisky?.realIndex, "shortName": self.title, "distillery": distilleryLabel.text] as [String : Any]
starRef2.updateChildValues(favourite, withCompletionBlock: { (err, ref) in
if err != nil {
print(err as Any)
return
} })}
else {
let image = UIImage(named: "filled 64x64.png")
(sender as AnyObject).setImage(image, for: UIControlState.highlighted)
let favourite = ["favourite": true, "realIndex": detailWhisky?.realIndex, "shortName": self.title, "distillery": distilleryLabel.text] as [String : Any]
starRef2.updateChildValues(favourite, withCompletionBlock: { (err, ref) in
if err != nil {
print(err as Any)
return
} })}
isStarred = !isStarred
UserDefaults.standard.set(isStarred, forKey: "isStarred")
UserDefaults.standard.synchronize()
}
Then to populate I run "starred()" in ViewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
configureView() //populates text fields etc.
starred() // populates the starred star
}
The "starred()" function looks like this:
//func starred(likeButton: UIButton) {
func starred() {
//initalize to make query work
let currentUserID = self.user?.uid //to get userID
let realIndex:Int? = detailWhisky?.realIndex
let realIndex2 = "\(realIndex!)" //realIndex
//query to firebase
starRef.child(currentUserID!).queryOrdered(byChild: "realIndex").queryEqual(toValue: realIndex).observe(.value, with: { snapshot in
if snapshot.value is NSNull {
print("Ingen treff i stjaernesoeket")
} else {
for child in snapshot.children {
let newStarred = Favourite(snapshot: child as! FIRDataSnapshot)
self.detailStarred = newStarred
//print(newStarred)
var boolStorage: Bool? = self.detailStarred?.favourite
if let boolStorage = boolStorage, boolStorage {
// executes when booleanValue is true
print("unwrapped boolStorage: '\(boolStorage)'")
let myImage = UIImage(named: "filled 64x64.png")
self.starButton.setImage(myImage, for: UIControlState.normal)
}
else {
print("not a favourite")}
}}})
}
Data struct looks like this:
struct Favourite {
var key: String?
let ref: FIRDatabaseReference?
var favourite: Bool?
var currentUserID: String?
var realIndex: Int?
var shortName: String?
let distillery: String?
init(key: String = "", favourite: Bool, currentUserID: String, realIndex: Int, shortName: String, distillery: String){
self.key = key
self.ref = nil
self.favourite = favourite
self.currentUserID = currentUserID
self.realIndex = realIndex
self.shortName = shortName
self.distillery = distillery
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
ref = snapshot.ref
let snapshotValue = snapshot.value as! [String: AnyObject]
favourite = snapshotValue["favourite"] as? Bool
currentUserID = snapshotValue["currentUserID"] as? String
realIndex = snapshotValue["realIndex"] as? Int
shortName = snapshotValue["shortName"] as? String
distillery = snapshotValue["distillery"] as? String
}
func toAnyObject() -> Any {
return [
"favourite": favourite as Any,
"currentUserID": currentUserID as Any,
"realIndex": realIndex as Any,
"shortName": shortName as Any,
"distillery": distillery as Any,
]
}
}
I found a solution that works for me. I am sure it can be handled more elegantly, but I am posting it in case it can help someone else.
The solution was to add an extra variable "buttonStarred" that gets a value from Firebase if its not favourited before. I then check this variable first when the "starred button" is pressed, before I continue with the existing code.
func that loads status of favorites from firebase. now it gives a value of "missing" to "buttonStarred" variable.
func starred() {
//initalize to make query work
let currentUserID = self.user?.uid //to get userID
let realIndex:Int? = detailWhisky?.realIndex
//query to firebase
starRef.child(currentUserID!).queryOrdered(byChild: "realIndex").queryEqual(toValue: realIndex).observe(.value, with: { snapshot in
if snapshot.value is NSNull {
print("Ingen treff i stjaernesoeket")
self.buttonStarred = "missing"
print(self.buttonStarred as Any)
} else { for child in snapshot.children {
let newStarred = Favourite(snapshot: child as! FIRDataSnapshot)
self.detailStarred = newStarred
//print(newStarred)
var boolStorage: Bool? = self.detailStarred?.favourite
if let boolStorage = boolStorage, boolStorage {
// executes when booleanValue is true
let myImage = UIImage(named: "filled 64x64.png")
self.starButton.setImage(myImage, for: UIControlState.normal)
self.buttonStarred = "fav"
print(self.buttonStarred)
}
else {
//self.detailStarred?.favourite = true
print("not a favourite")
self.buttonStarred = "notFav"
print(self.buttonStarred as Any)
}
}}})
}
then my button does this extra check:
if self.buttonStarred == "missing" { //extra check to avoid false to be the value for first time creation in FireBase
let favourite = ["favourite": true, "realIndex": detailWhisky?.realIndex as Any, "shortName": self.title as Any, "distillery": distilleryLabel.text as Any, "searchText": searchText as Any] as [String : Any] //21.09.0217
starRef2.updateChildValues(favourite, withCompletionBlock: { (err, ref) in
if err != nil {
print(err as Any)
return
} })}
And now it works. It fills the button at once, so its seamless for the user, and writes the correct status to firebase.
Full button code below in case it can help someone:
#IBAction func starButton(_ sender: Any) {
//loads info for query
let currentUserID = self.user?.uid
let realIndex:Int? = detailWhisky?.realIndex
let realIndex2 = "\(realIndex!)"
let searchText: String? = ("\(self.distilleryLabel.text)"+"\(self.title)"+"\(self.brandNameLabel.text)")//creates searchText
let filledImage = UIImage(named: "filled 64x64.png") //21.09.0217
//executes query
let starRef2 = FIRDatabase.database().reference().child("Starred").child(currentUserID!).child(realIndex2)
//print("booyah")
self.starButton.setImage(filledImage, for: UIControlState.normal)//21.09.0217
let favourite = ["favourite": true, "realIndex": detailWhisky?.realIndex as Any, "shortName": self.title as Any, "distillery": distilleryLabel.text as Any, "searchText": searchText as Any] as [String : Any] //21.09.0217
starRef2.updateChildValues(favourite, withCompletionBlock: { (err, ref) in
if err != nil {
print(err as Any)
return
}})
//changes state of button and loads true/false to Firebase
isStarred = !isStarred
if self.buttonStarred == "missing" { //extra check to avoid false to be the value for first time creation in FireBase
let favourite = ["favourite": true, "realIndex": detailWhisky?.realIndex as Any, "shortName": self.title as Any, "distillery": distilleryLabel.text as Any, "searchText": searchText as Any] as [String : Any] //21.09.0217
starRef2.updateChildValues(favourite, withCompletionBlock: { (err, ref) in
if err != nil {
print(err as Any)
return
} })}
else if isStarred == false {
let image = UIImage(named: "star64x64.png")
(sender as AnyObject).setImage(image, for: UIControlState.normal)
let favourite = ["favourite": false, "realIndex": detailWhisky?.realIndex as Any, "shortName": self.title as Any, "distillery": distilleryLabel.text as Any, "searchText": searchText as Any] as [String : Any]
starRef2.updateChildValues(favourite, withCompletionBlock: { (err, ref) in
if err != nil {
print(err as Any)
return
} })}
else {
let image = UIImage(named: "filled 64x64.png")
(sender as AnyObject).setImage(image, for: UIControlState.highlighted)
let favourite = ["favourite": true, "realIndex": detailWhisky?.realIndex as Any, "shortName": self.title as Any, "distillery": distilleryLabel.text as Any, "searchText": searchText as Any] as [String : Any]
starRef2.updateChildValues(favourite, withCompletionBlock: { (err, ref) in
if err != nil {
print(err as Any)
return
} })}
//isStarred = !isStarred
UserDefaults.standard.set(isStarred, forKey: "isStarred")
UserDefaults.standard.synchronize()
}

Resources