I want to delete a post, if the post has equal/more then 5 dislikes. I implemented the counter part and this works already. But the post will not be deleted even if I have 6 dislikes like on this post below:
Here you can see my database.
This is the code, I want to delete the posts after 5 dislikes:
// Dislike Button
func addTapGestureToDislikeImageView() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDidTapDislike))
dislikeImageView.addGestureRecognizer(tapGesture)
dislikeImageView.isUserInteractionEnabled = true
}
#objc func handleDidTapDislike() {
guard let postId = post?.id else { return }
PostApi.shared.incrementDislikes(postId: postId, onSuccess: { (post) in
self.updateDislike(post: post)
self.post?.dislikes = post.dislikes
self.post?.isDisliked = post.isDisliked
self.post?.dislikeCount = post.dislikeCount
}, onError: { (errorMessage) in
ProgressHUD.showError(errorMessage)
})
}
func updateDislike(post: PostModel) {
if post.isDisliked == false || post.dislikes == nil {
dislikeImageView.image = UIImage(named: "icons8-gefaellt-nicht-50")
} else {
dislikeImageView.image = UIImage(named: "icons8-gefaellt-nicht-filled-50")
}
guard let count = post.dislikeCount else { return }
if count >= 5 {
deletePost()
}
}
func deletePost() {
// Remove the post from the DB
let ref = Database.database().reference()
ref.child("posts").child((post?.id)!).removeValue { error,ref in
if error != nil {
print(error!.localizedDescription)
}
}
}
Here I increment Dislikes:
func incrementDislikes(postId id: String, onSuccess: #escaping (PostModel) -> Void, onError: #escaping (_ errorMessage: String?) -> Void) {
let postRef = REF_POSTS.child(id)
postRef.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if var post = currentData.value as? [String : AnyObject], let uid = UserApi.shared.CURRENT_USER_ID {
var dislikes: Dictionary<String, Bool>
dislikes = post["dislikes"] as? [String : Bool] ?? [:]
var dislikeCount = post["dislikeCount"] as? Int ?? 0
if let _ = dislikes[uid] {
// Unstar the post and remove self from stars
dislikeCount -= 1
dislikes.removeValue(forKey: uid)
} else {
// Star the post and add self to stars
dislikeCount += 1
dislikes[uid] = true
}
post["dislikeCount"] = dislikeCount as AnyObject?
post["dislikes"] = dislikes as AnyObject?
// Set value and report transaction success
currentData.value = post
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
onError(error.localizedDescription)
}
guard let dic = snapshot?.value as? [String: Any] else { return }
guard let postId = snapshot?.key else { return }
let updatePost = PostModel(dictionary: dic, key: postId)
onSuccess(updatePost)
}
}
Thanks for your help!
I think you need to share more information, or any error you see, or debug it and test where the action stops. But try this one maybe it will work.
#objc func handleDidTapDislike() {
guard let postId = post?.id else { return }
PostApi.shared.incrementDislikes(postId: postId, onSuccess: { (post) in
self.updateDislike(post: post)
self.post?.dislikes = post.dislikes
self.post?.isDisliked = post.isDisliked
self.post?.dislikeCount = post.dislikeCount
if post.dislikeCount > 4 {
deletePost()
}
}, onError: { (errorMessage) in
ProgressHUD.showError(errorMessage)
})}
and then edit updateDislike() function
func updateDislike(post: PostModel) {
if post.isDisliked == false || post.dislikes == nil {
dislikeImageView.image = UIImage(named: "icons8-gefaellt-nicht-50")
} else {
dislikeImageView.image = UIImage(named: "icons8-gefaellt-nicht-filled-50")
}
}
Related
Whenever paging in tableview, the view model is running fetchDataRx. It works well, but I don't think it's right to sort the entire data every time you paginate and call the addSnapShotListener. If my code is not correct, how can I correct it?
// MARK: ViewController.swift
timeLineTableView.rx.didScroll
.withLatestFrom(viewModel.activated)
.subscribe(onNext: { [weak self] isActivated in
if !isActivated {
guard let self = self else { return }
let position = self.timeLineTableView.contentOffset.y
if position > self.timeLineTableView.contentSize.height - 100 - self.timeLineTableView.frame.size.height {
self.viewModel.fetchPosts.onNext(())
}
}
})
.disposed(by: disposeBag)
//MARK: ViewModel.swift
let fetchPosts: AnyObserver<Void>
let fetching = PublishSubject<Void>()
fetchPosts = fetching.asObserver()
fetching
.do(onNext: { _ in activating.onNext(true) })
.withLatestFrom(posts)
.map { $0.count }
.flatMap{ (count) -> Observable<[post]> in
fireBaseService.fetchDataRx(startIdx: count) }
.map { $0.map { ViewPost(post: $0) } }
.do(onNext: { _ in activating.onNext(false) })
.do(onError: { err in error.onNext(err) })
.subscribe(onNext: { newPosts in
let oldData = posts.value
posts.accept(oldData + newPosts)
})
.disposed(by: disposeBag)
//MARK: FirebaseService.swift
protocol FirebaseServiceProtocol {
func fetchDataRx(startIdx: Int) -> Observable<[post]>
func fetchData(startIdx: Int, completion: #escaping (Result<[post], Error>) -> Void)
}
class FireBaseService: FirebaseServiceProtocol {
func fetchDataRx(startIdx: Int) -> Observable<[post]> {
return Observable.create { (observer) -> Disposable in
self.fetchData(startIdx: startIdx) { result in
switch result {
case .success(let data):
observer.onNext(data)
case .failure(let error):
observer.onError(error)
}
observer.onCompleted()
}
return Disposables.create()
}
}
func fetchData(startIdx: Int, completion: #escaping (Result<[post], Error>) -> Void) {
let db = Firestore.firestore()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm"
if startIdx == 0 {
DispatchQueue.global().async {
let first = db.collection("lolCourt")
.order(by: "date")
.limit(to: 8)
var nextPosts = [post]()
first.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
for document in querySnapshot!.documents {
guard let url = document.data()["url"] as? String else {
continue
}
guard let champion1 = document.data()["champion1"] as? String else {
continue
}
guard let champion1Votes = document.data()["champion1Votes"] as? Double else {
continue
}
guard let champion2 = document.data()["champion2"] as? String else {
continue
}
guard let champion2Votes = document.data()["champion2Votes"] as? Double else {
continue
}
guard let text = document.data()["text"] as? String else {
continue
}
guard let date = document.data()["date"] as? Double else {
continue
}
nextPosts.append(post(url: url,
champion1: champion1,
champion1Votes: champion1Votes,
champion2: champion2,
champion2Votes: champion2Votes,
text: text,
date: formatter.string(from: Date(timeIntervalSince1970: date))))
}
}
completion(.success(nextPosts))
}
}
}
else {
DispatchQueue.global().async {
let first = db.collection("lolCourt")
.order(by: "date")
.limit(to: startIdx)
first.addSnapshotListener { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error retrieving : \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
return
}
let next = db.collection("lolCourt")
.order(by: "date")
.start(afterDocument: lastSnapshot)
.limit(to: 8)
var nextPosts = [post]()
next.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
for document in querySnapshot!.documents {
guard let url = document.data()["url"] as? String else {
continue
}
guard let champion1 = document.data()["champion1"] as? String else {
continue
}
guard let champion1Votes = document.data()["champion1Votes"] as? Double else {
continue
}
guard let champion2 = document.data()["champion2"] as? String else {
continue
}
guard let champion2Votes = document.data()["champion2Votes"] as? Double else {
continue
}
guard let text = document.data()["text"] as? String else {
continue
}
guard let date = document.data()["date"] as? Double else {
continue
}
nextPosts.append(post(url: url,
champion1: champion1,
champion1Votes: champion1Votes,
champion2: champion2,
champion2Votes: champion2Votes,
text: text,
date: formatter.string(from: Date(timeIntervalSince1970: date))))
}
}
completion(.success(nextPosts))
}
}
}
}
}
}
I think you are doing it wrong... I would expect to see something more like this:
class FireBaseService {
func getPage<T>(query: Query? = nil, build: #escaping ([String: Any]) -> T) -> Observable<([T], nextPage: Query?)> {
Observable.create { observer in
let db = Firestore.firestore()
let page = query ?? db.collection("lolCourt")
.order(by: "date")
.limit(to: 8)
let listener = page
.addSnapshotListener { snapshot, error in
guard let snapshot = snapshot else { observer.onError(error ?? RxError.unknown); return }
let items = snapshot.documents.map { build($0.data()) }
if let lastSnapshot = snapshot.documents.last {
let next = page
.start(afterDocument: lastSnapshot)
observer.onSuccess((items, nextPage: next))
}
else {
observer.onSuccess((items, nextPage: nil))
}
}
return Disposables.create { listener.remove() }
}
}
}
Use the above in your favorite state machine system. Here is an example using my CLE library.
// in view controller
let fireBaseService = FireBaseService()
let activityIndicator = ActivityIndicator()
let errorRouter = ErrorRouter()
func getPage(nextPage: Query?) -> Observable<([Post?], nextPage: Query?)> {
fireBaseService.getPage(query: nextPage, build: Post.init(dict:))
.rerouteError(errorRouter)
.trackActivity(activityIndicator)
}
let posts = cycle(
inputs: [
getPage(nextPage: nil).map(ViewModel.Input.response),
timeLineTableView.rx.reachedBottom(offset: 20).map(to: ViewModel.Input.next)
],
initialState: ([Post?](), nextPage: Query?.none),
environment: getPage(nextPage:),
reduce: ViewModel.reduce(state:input:getPage:)
)
.map { $0.0.compactMap { $0 } }
and the view model:
enum ViewModel {
enum Input {
case response([Post?], nextPage: Query?)
case next
}
static func reduce(state: inout ([Post?], nextPage: Query?), input: Input, getPage: #escaping (Query) -> Observable<([Post?], nextPage: Query?)>) -> Observable<Input> {
switch input {
case let .response(posts, nextPage):
state.0 += posts
state.nextPage = nextPage
case .next:
guard let nextPage = state.nextPage else { break }
return getPage(nextPage)
.map(Input.response)
}
return .empty()
}
}
I have this function. Whenever I comment the "v": 2 parameter it works like a charm, then I add it and when trying to call the function from my VC it doesn't get fired off. I think the problem is somewhere at the top. Like I said, when commenting the "v:2" parameter, everything goes smooth. I've checked my postman and the URL Request adding that parameter does give me the right response, but my function is not being called in my VC. Any ideas? Please I'm desperate ):
public func getBusinessBy(location loc: String, type: Int, loading: Bool, OnSuccess success: #escaping (_ businesses: [NSDictionary]) -> Void, OnFailed failed: #escaping (_ error: String) -> Void) {
if loading {
GlobalLoader.show()
}
let bUrl = HttpUtils.getBusinessesBy(type: type)
let params: Parameters = [
"location": loc,
"type": type,
"v": 2,
"params": Config.ENABLE_SEARCH ? Config.SEARCH_BUSINESS_PARAMS : Config.COMMON_BUSINESS_PARAMS,
]
Alamofire.request(bUrl, method: .get, parameters: params, encoding: URLEncoding.queryString, headers: [:]).responseJSON() { response in
switch response.result {
case .success(let val):
print("yaaaaay!")
if let res = val as? NSDictionary {
if let businesses = res.results() {
let allBusiesses = businesses.sorted(by: { (d1, d2) -> Bool in
(d1.object(forKey: "open") as! Bool) && !(d2.object(forKey: "open") as! Bool)
})
// Storing All Businesses to global
Session.sharedInstance.setAllBusiness(allBusiesses)
var tmpCat = [NSDictionary]()
var tmpPro = [NSDictionary]()
var tmpPMotion = [NSDictionary]()
var tmpPBusiness = [NSDictionary]()
for busin in allBusiesses {
guard let categories = busin["categories"] as? [NSDictionary] else {
return
}
tmpCat.append(contentsOf: categories)
var flag = false
for cat in categories {
if let prod = cat["products"] as? [NSDictionary] {
for p in prod {
var pClone = NSMutableDictionary()
pClone = p.mutableCopy() as! NSMutableDictionary
let pivot = NSMutableDictionary()
pivot["business_id"] = busin.getId()
pivot["business_name"] = busin.getName()
pivot["business_description"] = busin.getDescription()
pivot["business_enabled"] = busin.isOpened()
pivot["category_name"] = cat.getName()
pivot["category_description"] = cat.getDescription()
pivot["category_enabled"] = cat.isEnabled()
pClone["pivot"] = pivot
tmpPro.append(pClone)
if p.isFeatured() {
tmpPMotion.append(pClone)
if !flag {
tmpPBusiness.append(busin)
flag = true
}
}
}
}
}
}
Session.sharedInstance.setAllProduct(tmpPro)
Session.sharedInstance.setPromotions(tmpPMotion)
Session.sharedInstance.setPromBusinesses(tmpPBusiness)
Session.sharedInstance.setAllCategory(tmpCat)
// }
success(businesses)
}
} else {
failed(val as? String ?? "Passing error!")
}
if loading {
// SVProgressHUD.dismiss()
GlobalLoader.hide()
}
break
case .failure(let error):
print("naaaaay!")
failed(error.localizedDescription)
if loading {
// SVProgressHUD.dismiss()
GlobalLoader.hide()
}
break
}
}
}
I have a search function in my app which works perfectly except for the fact that it seems to be one reload behind. For example if I type in 'S' nothing will show but if I then type an 'a' after all the results for 'S' will show up. I tried adding collectionView reload at the end of the fetch function but if I do that nothing shows up period.
#objc func textFieldDidChange(_ textField: UITextField) {
guard let text = textField.text else { return }
if text.count == 0 {
self.posts.removeAll()
self.filteredPosts.removeAll()
} else {
fetchSearchedPosts(searchTerm: text)
}
self.collectionView.reloadData()
}
func fetchSearchedPosts(searchTerm: String) {
self.collectionView.refreshControl?.endRefreshing()
let ref = Database.database().reference().child("posts").queryOrdered(byChild: "title").queryStarting(atValue: searchTerm).queryEnding(atValue: "\(searchTerm)\u{f8ff}")
ref.observeSingleEvent(of: .value) { (snapshot) in
if !snapshot.exists() { return }
guard let dictionaries = snapshot.value as? [String: Any] else { return }
self.posts.removeAll()
dictionaries.forEach({ (key, value) in
guard let postDictionary = value as? [String: Any] else { return }
guard let uid = postDictionary["uid"] as? String else { return }
Database.fetchUserWithUID(uid: uid, completion: { (user) in
let post = Post(postId: key, user: user, dictionary: postDictionary)
let nowTimeStamp = Date().timeIntervalSince1970
let dateTime = post.endTimeDate
let timeStamp = dateTime.timeIntervalSince1970
if nowTimeStamp < timeStamp {
post.id = key
self.posts.append(post)
} else {
return
}
})
self.posts.sort(by: { (post1, post2) -> Bool in
return post1.title.compare(post2.title) == .orderedAscending
})
})
self.collectionView.reloadData()
}
}
You need a DispatchGroup as you have nested asynchronous calls
func fetchSearchedPosts(searchTerm: String) {
self.collectionView.refreshControl?.endRefreshing()
let ref = Database.database().reference().child("posts").queryOrdered(byChild: "title").queryStarting(atValue: searchTerm).queryEnding(atValue: "\(searchTerm)\u{f8ff}")
ref.observeSingleEvent(of: .value) { (snapshot) in
if !snapshot.exists() { return }
guard let dictionaries = snapshot.value as? [String: Any] else { return }
self.posts.removeAll()
let g = DispatchGroup() ///// 1
dictionaries.forEach({ (key, value) in
guard let postDictionary = value as? [String: Any] else { return }
guard let uid = postDictionary["uid"] as? String else { return }
g.enter() ///// 2
Database.fetchUserWithUID(uid: uid, completion: { (user) in
let post = Post(postId: key, user: user, dictionary: postDictionary)
let nowTimeStamp = Date().timeIntervalSince1970
let dateTime = post.endTimeDate
let timeStamp = dateTime.timeIntervalSince1970
if nowTimeStamp < timeStamp {
post.id = key
self.posts.append(post)
} else {
g.leave() ///// 3.a
return
}
g.leave() ///// 3.b
})
})
g.notify(queue:.main) { ///// 4
self.posts.sort(by: { (post1, post2) -> Bool in
return post1.title.compare(post2.title) == .orderedAscending
})
self.collectionView.reloadData()
}
}
}
My app has an option to save posts for users to watch later. The code is:
func savedPost(for cell: FirstView) {
guard let indexPath = collectionView.indexPath(for: cell) else { return }
var post = self.posts[indexPath.item]
guard let currentuserId = Auth.auth().currentUser?.uid else { return }
let targetUid = post.user.uid
guard let postId = post.id else { return }
let ref = Database.database().reference().child("save_post").child(currentuserId).child(postId)
if post.hasSaved {
ref.removeValue { (err, _) in
if let _ = err {
showErr(info: NSLocalizedString("failtoUnsave", comment: ""), subInfo: tryLater)
return
}
post.hasSaved = false
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
} else {
let values = ["userId": targetUid]
ref.updateChildValues(values) { (err, ref) in
if let _ = err {
showErr(info: NSLocalizedString("failtoSave", comment: ""), subInfo: tryLater)
}
post.hasSaved = true
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
}
}
With this code my firebase database in "save_post" has -> currentUseruId -> postid -> postUserId.
On ProfileController users can view saved Posts from "savedPost" Tab. The code is:
var savedPosts = [Post]()
fileprivate func fetchSaved() {
var userIds = [String]()
var postIds = [String]()
guard let uid = self.user?.uid else { return }
let getIDsRef = Database.database().reference().child("save_post").child(uid)
let query = getIDsRef.queryOrderedByKey()
query.observeSingleEvent(of: .value) { (snapshot) in
let dictionary = snapshot.value as? [String: Any]
dictionary?.forEach({ (key,value) in
guard let dic = value as? [String: String] else { return }
postIds.append(key)
userIds.append(dic["userId"] ?? "")
})
var i = 0
while i < userIds.count {
self.fetchPostsWithUserIDPostID(userID: userIds[i], postID: postIds[i])
i += 1
}
}
}
fileprivate func fetchPostsWithUserIDPostID(userID: String, postID: String) {
let getPostRef = Database.database().reference().child("video_list")
getPostRef.child(userID).child(postID).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
let ref = Database.database().reference().child("users").child(userID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let user = User(uid: userID, dictionary: dict)
var post = Post(user: user, dictionary: dictionary)
post.id = postID
guard let currentUserUID = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("likes").child(postID).child(currentUserUID).observeSingleEvent(of: .value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
post.hasSaved = true
self.savedPosts.append(post)
self.savedPosts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView.reloadData()
})
})
})
}
However, when I click "savedPost" tab, there is no post shown. I don't know where my mistake is. I have all the necessary code under all override func collectionView(....). I believe the error should come from the code listed above. I am sincerely looking for help to resolve this issue. Thanks.
There could be a number of things going on here. It would be good to throw some print statements in there to make sure that 1) the data you're getting back from the database looks like what you expect, and 2) that you're properly parsing it into Post objects. Do you have your cells defined properly for your CollectionView? Also, I don't see where you are defining the data source for the CollectionView.
I'm creating a chat app using the MessageKit library in Swift 4.2 and FireStore.
My problem is that I can't store data acquired by real time communication using addSnapshotListener in the Message class and confirmed the contents of the class, but I do not think it is wrong.
messageListener is working properly. When handleDocumentChange is executed, it returns nil.
I checked the return value with document.data (), but the value returned.
How do I store values by referring to classes?
guard var message = Message (document: change.document) else {
print ("return Message")
return
}
The data entered for FireStore is as follows
{
"channels": [{
"MOuL1sdbrnh0x1zGuXn7": { // channel id
"name": "Puppies",
"thread": [{
"3a6Fo5rrUcBqhUJcLsP0": { // message id
"content": "Wow, that's so cute!",
"created": "May 12, 2018 at 10:44:11 PM UTC-5",
"senderID": "YCrPJF3shzWSHagmr0Zl2WZFBgT2",
"senderUsername": "naturaln0va",
"recipientProfilePictureURL":"URL",
"recipientID":"ezample",
"recipientUsername" :"A"
"recipientProfilePictureURL":"aaaaa"
},
}]
},
}]
}
That's my message class:
class Message: MessageType {
var id: String?
var sentDate: Date
var kind: MessageKind
lazy var sender: Sender = Sender(id: atcSender.uid ?? "No Id", displayName: atcSender.uid ?? "No Name")
var atcSender: User
var recipient: User
var messageId: String {
return id ?? UUID().uuidString
}
var image: UIImage? = nil
var downloadURL: URL? = nil
let content: String
init(messageId: String, messageKind: MessageKind, createdAt: Date, atcSender: User, recipient: User) {
self.id = messageId
self.kind = messageKind
self.sentDate = createdAt
self.atcSender = atcSender
self.recipient = recipient
switch messageKind {
case .text(let text):
self.content = text
default:
self.content = ""
}
}
init(user: User, image: UIImage) {
self.image = image
content = ""
sentDate = Date()
id = nil
self.kind = MessageKind.text("xxx")
self.atcSender = user
self.recipient = user
}
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard let sentDate = data["created"] as? Date else {
return nil
}
guard let senderID = data["senderID"] as? String else {
return nil
}
guard let senderUsername = data["senderUsername"] as? String else {
return nil
}
guard let senderProfilePictureURL = data["senderProfilePictureURL"] as? String else {
return nil
}
guard let recipientID = data["recipientID"] as? String else {
return nil
}
guard let recipientUsername = data["recipientUsername"] as? String else {
return nil
}
guard let recipientProfilePictureURL = data["recipientProfilePictureURL"] as? String else {
return nil
}
id = document.documentID
self.sentDate = sentDate
self.atcSender = User(uid: senderID, username: senderUsername, firstname: "", lastname: "", email: "", profileUrl: senderProfilePictureURL)
self.recipient = User(uid: recipientID, username: recipientUsername, firstname: "", lastname: "", email: "", profileUrl: recipientProfilePictureURL)
if let content = data["content"] as? String {
self.content = content
downloadURL = nil
} else if let urlString = data["url"] as? String, let url = URL(string: urlString) {
downloadURL = url
self.content = ""
} else {
return nil
}
self.kind = MessageKind.text(content)
}
required init(jsonDict: [String: Any]) {
fatalError()
}
var description: String {
return self.messageText
}
var messageText: String {
switch kind {
case .text(let text):
return text
default:
return ""
}
}
var channelId: String {
let id1 = (recipient.username ?? "")
let id2 = (atcSender.username ?? "")
return id1 < id2 ? id1 + id2 : id2 + id1
}
}
extension Message: DatabaseRepresentation {
var representation: [String : Any] {
var rep: [String : Any] = [
"created": sentDate,
"senderID": atcSender.uid ?? "",
"senderUsername": atcSender.username ?? "",
"senderProfilePictureURL": atcSender.profileUrl ?? "",
"recipientID": recipient.uid ?? "",
"recipientUsername": recipient.username ?? "",
"recipientProfilePictureURL": recipient.profileUrl ?? "",
]
if let url = downloadURL {
rep["url"] = url.absoluteString
} else {
rep["content"] = content
}
return rep
}}extension Message: Comparable {
static func == (lhs: Message, rhs: Message) -> Bool {
return lhs.id == rhs.id
}
static func < (lhs: Message, rhs: Message) -> Bool {
return lhs.sentDate < rhs.sentDate
}
}
ChatViewController:
import UIKit
import MessageKit
import MessageInputBar
import Firebase
import FirebaseFirestore
import FirebaseAuth
class ChatViewController: MessagesViewController {
private let db = Firestore.firestore()
private var reference: CollectionReference?
private var messages: [Message] = []
private var messageListener: ListenerRegistration?
private let user: User
private let channel: Channel
let uid = Auth.auth().currentUser?.uid
init(user: User, channel: Channel) {
self.user = user
self.channel = channel
super.init(nibName: nil, bundle: nil)
title = channel.name
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
messageListener?.remove()
}
override func viewDidLoad() {
super.viewDidLoad()
guard let id = channel.id else {
navigationController?.popViewController(animated: true)
return
}
reference = db.collection(["channels", id, "thread"].joined(separator: "/"))
// reference?.addSnapshotListener { querySnapshot, error in
// guard let snapshot = querySnapshot else {
// print("Error fetching snapshots: \(error!)")
// return
// }
// snapshot.documentChanges.forEach { diff in
// if (diff.type == .added) {
// print("New city: \(diff.document.data())")
// }
// if (diff.type == .modified) {
// print("Modified city: \(diff.document.data())")
// }
// if (diff.type == .removed) {
// print("Removed city: \(diff.document.data())")
// }
// }
// }
messageListener = reference?.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")")
return
}
snapshot.documentChanges.forEach { change in
self.handleDocumentChange(change)
print("handleDocumentChange")
}
}
self.navigationItem.title = title
messageInputBar.delegate = self
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messagesLayoutDelegate = self
messagesCollectionView.messagesDisplayDelegate = self
messageInputBar.sendButton.tintColor = UIColor.lightGray
//scrollsToBottomOnKeyboardBeginsEditing = true // default false
//maintainPositionOnKeyboardFrameChanged = true // default false
}
private func save(_ message: Message) {
reference?.addDocument(data: message.representation) { error in
if let e = error {
print("Error sending message: \(e.localizedDescription)")
return
}
self.messagesCollectionView.scrollToBottom()
}
}
private func insertNewMessage(_ message: Message) {
guard !messages.contains(message) else {
return
}
messages.append(message)
messages.sort()
let isLatestMessage = messages.index(of: message) == (messages.count - 1)
let shouldScrollToBottom = messagesCollectionView.isAtBottom && isLatestMessage
messagesCollectionView.reloadData()
if shouldScrollToBottom {
DispatchQueue.main.async {
self.messagesCollectionView.scrollToBottom(animated: true)
}
}
}
private func handleDocumentChange(_ change: DocumentChange) {
guard var message = Message(document: change.document) else {
print("return Message")
return
}
switch change.type {
case .added:
print("add Message")
insertNewMessage(message)
default:
break
}
}
}
Console prints
New city: ["senderUsername": panyayan,
"senderID": RAMIqHAVeoU4TKkm3FDw7XUwgym2,
"created": FIRTimestamp:seconds=1544623185 nanoseconds=412169933>,
"recipientUsername": panyayan,
"content": AAA,
"recipientID": RAMIqHAVeoU4TKkm3FDw7XUwgym2,
"recipientProfilePictureURL": https:,
"senderProfilePictureURL": https:]
return Message
handleDocumentChange
Dont know if you still need it but:
The document.data() field created is a FIRTimestamp.
When you try to init the Message object you use
guard let sentDate = data["created"] as? Date else {
return nil
}
Thats might be a reason, why your object is nil.
Try something like
guard let sentTimestamp = data["created"] as? Timestamp else {
return nil
}
...
self.sentDate = sentTimestamp.dateValue()