Using an older version of MessageKit, I made some changes to the library to get rid of the errors due to it being written in an older Swift version.
I integrated the Chat App into my own project, but the message text does not leave the message input bar when the send button is clicked.
Message.Swift File:
struct Message: MessageType {
let id: String?
let content: String
let sentDate: Date
let sender: SenderType
var kind: MessageKind {
if let image = image {
return .photo(image as! MediaItem)
} else {
return .text(content)
}
}
var messageId: String {
return id ?? UUID().uuidString
}
var image: UIImage? = nil
var downloadURL: URL? = nil
init(user: User, content: String) {
sender = Sender(id: user.uid, displayName: AppSettings.displayName)
self.content = content
sentDate = Date()
id = nil
}
init(user: User, image: UIImage) {
sender = Sender(id: user.uid, displayName: AppSettings.displayName)
self.image = image
content = ""
sentDate = Date()
id = nil
}
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 senderName = data["senderName"] as? String else {
return nil
}
id = document.documentID
self.sentDate = sentDate
sender = Sender(id: senderID, displayName: senderName)
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
content = ""
} else {
return nil
}
}
}
extension Message: DatabaseRepresentation {
var representation: [String : Any] {
var rep: [String : Any] = [
"created": sentDate,
"senderID": sender.senderId,
"senderName": sender.displayName
]
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:
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()
}
}
extension ChatViewController: MessagesLayoutDelegate {
func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
return .zero
}
func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
return CGSize(width: 0, height: 8)
}
func heightForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
return 0
}
}
extension ChatViewController: MessagesDataSource {
func currentSender() -> SenderType {
return Sender(id: user.uid, displayName: AppSettings.displayName)
}
func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int {
return messages.count
}
func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int {
return messages.count
}
func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType {
return messages[indexPath.section]
}
func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
let name = message.sender.displayName
return NSAttributedString(
string: name,
attributes: [
.font: UIFont.preferredFont(forTextStyle: .caption1),
.foregroundColor: UIColor(white: 0.3, alpha: 1)
]
)
}
}
extension ChatViewController: MessageInputBarDelegate {
func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
let message = Message(user: user, content: text)
save(message)
inputBar.inputTextView.text = ""
}
my viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
guard let id = channel.id else {
navigationController?.popViewController(animated: true)
return
}
reference = db.collection(["channels", id, "thread"].joined(separator: "/"))
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)
}
}
messageInputBar.delegate = self
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messagesLayoutDelegate = self
messagesCollectionView.messagesDisplayDelegate = self
}
It seems, that your pod or carthage file use messageKit with latest version like "3.0.0". So, there is some changes in delegate.
Try to change this peace of code:
extension ChatViewController: MessageInputBarDelegate {
func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
let message = Message(user: user, content: text)
save(message)
inputBar.inputTextView.text = ""
}
with code below
extension ChatViewController: MessageInputBarDelegate {
func inputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
let message = Message(user: user, content: text)
save(message)
inputBar.inputTextView.text = ""
}
}
If you want to use your old syntax, set messageKit version to some old in your pod or carthage file.
Related
So far I am able to add a snapshotlistener to the collection:
db.collection("products/country/class/grade1/test").order(by: "qId").addSnapshotListener { [self] (querySnapshot, error) in
//Handle Error:
if let error = error {
print("Error getting documents: \(error.localizedDescription)")
} else {
//No Documents Found:
guard let documents = querySnapshot?.documents else {
return
}
documents.compactMap { doc in
let value = doc.data()
print (value)
}
}
}
However, I would like it where a little badge appears showing that there were databases changes & when the user presses the update button it loads only the changed (added or updated) documents
class ChannelsViewController: UITableViewController {
private var channelReference: CollectionReference {
return database.collection("channels")
}
private var channels: [Channel] = []
override func viewDidLoad() {
super.viewDidLoad()
channelListener = channelReference.addSnapshotListener { [weak self]
querySnapshot, error in
guard let self = self else { return }
guard let snapshot = querySnapshot else {
print("Error listening for channel updates: \.
(error?.localizedDescription ?? "No error")")
return
}
snapshot.documentChanges.forEach { change in
self.handleDocumentChange(change)
}
}
}
private func addChannelToTable(_ channel: Channel) {
if channels.contains(channel) {
return
}
channels.append(channel)
channels.sort()
guard let index = channels.firstIndex(of: channel) else {
return
}
tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
private func updateChannelInTable(_ channel: Channel) {
guard let index = channels.firstIndex(of: channel) else {
return
}
channels[index] = channel
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
private func removeChannelFromTable(_ channel: Channel) {
guard let index = channels.firstIndex(of: channel) else {
return
}
channels.remove(at: index)
tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
private func handleDocumentChange(_ change: DocumentChange) {
guard let channel = Channel(document: change.document) else {
return
}
switch change.type {
case .added:
addChannelToTable(channel)
case .modified:
updateChannelInTable(channel)
case .removed:
removeChannelFromTable(channel)
}
}
}
this is an example automatically update the tableView when add,update and delete on collection name "channel"
and the Channel as:
import FirebaseFirestore
struct Channel {
let id: String?
let name: String
init(name: String) {
id = nil
self.name = name
}
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard let name = data["name"] as? String else {
return nil
}
id = document.documentID
self.name = name
}
}
// MARK: - DatabaseRepresentation
extension Channel: DatabaseRepresentation {
var representation: [String: Any] {
var rep = ["name": name]
if let id = id {
rep["id"] = id
}
return rep
}
}
// MARK: - Comparable
extension Channel: Comparable {
static func == (lhs: Channel, rhs: Channel) -> Bool {
return lhs.id == rhs.id
}
static func < (lhs: Channel, rhs: Channel) -> Bool {
return lhs.name < rhs.name
}
}
This is my code and only thumbnailImageForActivityType is not called...
class ShareMovieEvent: NSObject, UIActivityItemSource {
let movieDate: String
let movieTitle: String
let movieDescription: String
let imageHeader: UIImage
init(movieDate: String ,movieTitle:String, movieDescription:String, imageHeader:UIImage) {
self.movieDate = movieDate
self.movieTitle = movieTitle
self.movieDescription = movieDescription
self.imageHeader = imageHeader
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return ""
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
if activityType == UIActivity.ActivityType.message {
return "\(movieDate) \n\n \(movieTitle) \n\n \(movieDescription)"
} else if activityType == UIActivity.ActivityType.mail {
return "\(movieDate) \n\n \(movieTitle) \n\n \(movieDescription)"
}
return nil
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
if activityType == UIActivity.ActivityType.message {
return ""
} else if activityType == UIActivity.ActivityType.mail {
return "Check this event"
}
return ""
}
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
return imageHeader
}
}
And here is the call in my ViewModel class
#objc func shareBtnPressed(_ sender: UIButton){
let share = ShareMovieEvent(movieDate: movie.date, movieTitle: movie.title, movieDescription: movie.movieDescription, imageHeader: imageHeaderMovie.image!)
let viewController = UIActivityViewController(activityItems: [share], applicationActivities: [])
self.pushableViewControllerObservable?(viewController)
}
I've been using NewsApi . and it works perfectly but all of a sudden I got this error :
Thread 10: Fatal error: unableToParse
is anyone familiar with this error
my view controller :
class HomeViewController: UIViewController , UITableViewDelegate, UITableViewDataSource ,UIScrollViewDelegate , UITabBarDelegate {
var ref: DatabaseReference!
var i: Int = 0
#IBOutlet weak var bookMark: UITabBarItem!
// outlets
#IBOutlet weak var tableView: UITableView!
var articles = [NewsArticle]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
let newsAPI = NewsAPI(apiKey: "//////////")
private let imageView = UIImageView(image: UIImage(named: "logo"))
//// table View
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.row == 0)
{
let cell = Bundle.main.loadNibNamed("FeaturesTableViewCell", owner: self, options: nil)?.first as! FeaturesTableViewCell
return cell
}
else if (indexPath.row == 1)
{
var breakingNews : String = ""
let cell = Bundle.main.loadNibNamed("BreakingNewsViewCell", owner: self, options: nil)?.first as! BreakingNewsViewCell
for article in articles
{
breakingNews = breakingNews+" - "+article.title
}
cell.newsLabel.text = breakingNews
return cell
}
else{
let cell = Bundle.main.loadNibNamed("NewViewCellTableViewCell", owner: self, options: nil)?.first as! NewViewCellTableViewCell
cell.titleLabel.text = "NEWS"
return cell
}
}
override func viewDidLoad() {
navigationItem.hidesBackButton = true
newsAPI.getTopHeadlines(q:"a"){ result in
switch result {
case .success(let articles):
self.articles = articles
case .failure(let error):
fatalError("\(error)")
}
}
tableView.delegate = self
tableView.dataSource = self
// setupMenuBar()
}
}
and i got that error marked in :
case .failure(let error):
but it used work perfectly
NewsApi Class:
public typealias NewsAPIRequestHandler<T> = ((Result<[T], NewsAPIError>) -> ())
public class NewsAPI {
private let provider: NewsProvider
private let decoder: NewsAPIDecoder
public init(apiKey: String) {
self.provider = NewsProvider(apiKey: apiKey)
self.decoder = NewsAPIDecoder()
}
init(provider: NewsProvider, sourceDecoder: NewsAPIDecoder) {
self.provider = provider
self.decoder = sourceDecoder
}
#discardableResult
public func getSources(category: NewsCategory = .all, language: NewsLanguage = .all, country: NewsCountry = .all, completion: #escaping NewsAPIRequestHandler<NewsSource>) -> URLSessionDataTask? {
let targetAPI = NewsAPITarget.sources(category: category, language: language, country: country)
return request(targetAPI, completion: completion)
}
#discardableResult
public func getTopHeadlines(q: String? = nil,
sources: [String]? = nil,
category: NewsCategory = .all,
language: NewsLanguage = .all,
country: NewsCountry = .all,
pageSize: Int? = nil,
page: Int? = nil,
completion: #escaping NewsAPIRequestHandler<NewsArticle>) -> URLSessionDataTask? {
let targetAPI = NewsAPITarget.topHeadlines(q: q, sources: sources, category: category, language: language, country: country, pageSize: pageSize, page: page)
return request(targetAPI, completion: completion)
}
}
private extension NewsAPI {
func request<T: Decodable>(_ target: NewsAPITarget, completion: #escaping NewsAPIRequestHandler<T>) -> URLSessionDataTask? {
return provider.request(target) { data, error in
guard let data = data else {
completion(.failure(error ?? .unknown))
return
}
do {
let result: [T] = try self.decoder.decode(data: data)
completion(.success(result))
} catch let error {
let newsAPIError = (error as? NewsAPIError) ?? .unknown
completion(.failure(newsAPIError))
}
}
}
}
and as for newsAPIError class it has :
public enum NewsAPIError: Error {
case unknown
case unableToParse
case requestFailed
case invalidEndpointUrl
case serviceError(code: String, message: String)
}
please any help is welcome and thank you
I have implemented private chat in my ios app. However, it is not so private. When I send a message that I intend to send to one person, everyone in the app can see it. I have three view controllers in play here.
The FirstViewController has a list of users, and when the cell is clicked it is segued to the DetailedViewController. In this viewController, it only lists the details of the user clicked on. Next, when I press the compose button in the DetailedViewController, the goal is to segue to MessageUserController. This is where I am stuck. This is the code to segue to the MessageUserController:
var username: String?
#IBAction func sendMessage(_ sender: Any) {
performSegue(withIdentifier: "sendMessageToUser", sender: self.username)
}
override public func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "sendMessageToUser", let chatVc = segue.destination as? MessageViewController else {
return
}
chatVc.senderId = self.loggedInUser?.uid
chatVc.senderDisplayName = self.username
}
I assume the sender could be the username because it is unique to the user. When I click on a user to chat with, it works fine but when I click on another user, the chat between the first users are already displayed in the new user's chatController
In the firstViewController, username is passed like this:
if segue.identifier == "UsersProfile" {
if let indexPath = sender as? IndexPath{
let vc = segue.destination as! UsersProfileViewController
let post = self.posts[indexPath.row] as! [String: AnyObject]
let username = post["username"] as? String
vc.username = username
}
}
entire view controller:
import UIKit
import Photos
import Firebase
import FirebaseDatabase
import JSQMessagesViewController
class SendMessageViewController: JSQMessagesViewController {
var username: String?
//var receiverData = AnyObject?()
var messages = [JSQMessage]()
private var photoMessageMap = [String: JSQPhotoMediaItem]()
private let imageURLNotSetKey = "NOTSET"
lazy var outgoingBubbleImageView: JSQMessagesBubbleImage = self.setupOutgoingBubble()
lazy var incomingBubbleImageView: JSQMessagesBubbleImage = self.setupIncomingBubble()
var rootRef = FIRDatabase.database().reference()
var messageRef = FIRDatabase.database().reference().child("messages")
private var newMessageRefHandle: FIRDatabaseHandle?
private lazy var usersTypingQuery: FIRDatabaseQuery =
self.rootRef.child("typingIndicator").queryOrderedByValue().queryEqual(toValue: true)
lazy var storageRef: FIRStorageReference = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
private var updatedMessageRefHandle: FIRDatabaseHandle?
private lazy var userIsTypingRef: FIRDatabaseReference =
self.rootRef.child("typingIndicator").child(self.senderId) // 1
private var localTyping = false // 2
var isTyping: Bool {
get {
return localTyping
}
set {
// 3
localTyping = newValue
userIsTypingRef.setValue(newValue)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.senderId = FIRAuth.auth()?.currentUser?.uid
// Do any additional setup after loading the view.
self.navigationController?.navigationBar.barTintColor = UIColor(red:0.23, green:0.73, blue:1.00, alpha:1.0)
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
self.navigationItem.title = senderDisplayName
self.navigationItem.rightBarButtonItem?.tintColor = UIColor.white
self.navigationItem.leftBarButtonItem?.tintColor = UIColor.white
// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
observeMessages()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeTyping()
}
deinit {
if let refHandle = newMessageRefHandle {
messageRef.removeObserver(withHandle: refHandle)
}
if let refHandle = updatedMessageRefHandle {
messageRef.removeObserver(withHandle: refHandle)
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
private func setupOutgoingBubble() -> JSQMessagesBubbleImage {
let bubbleImageFactory = JSQMessagesBubbleImageFactory()
return bubbleImageFactory!.outgoingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue())
}
private func setupIncomingBubble() -> JSQMessagesBubbleImage {
let bubbleImageFactory = JSQMessagesBubbleImageFactory()
return bubbleImageFactory!.incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleLightGray())
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.senderId == senderId { // 2
return outgoingBubbleImageView
} else { // 3
return incomingBubbleImageView
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAt indexPath: IndexPath!) -> CGFloat {
return 15
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId {
cell.textView?.textColor = UIColor.white
} else {
cell.textView?.textColor = UIColor.black
}
return cell
}
//ADD A NEW MESSAGE
private func addMessage(withId id: String, name: String, text: String) {
if let message = JSQMessage(senderId: id, displayName: name, text: text) {
messages.append(message)
}
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
let itemRef = rootRef.child("messages").childByAutoId() // 1
let messageItem = [ // 2
"senderId": senderId!,
"ReceiverName": senderDisplayName!,
"text": text!,
]
itemRef.setValue(messageItem) // 3
JSQSystemSoundPlayer.jsq_playMessageSentSound() // 4
finishSendingMessage() // 5
isTyping = false
}
private func observeMessages() {
// 1.
let messageQuery = rootRef.child("messages").queryLimited(toLast: 25)
// 2. We can use the observe method to listen for new
// messages being written to the Firebase DB
newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) -> Void in
// 3
let messageData = snapshot.value as! Dictionary<String, String>
if let id = messageData["senderId"] as String!, let name = messageData["ReceiverName"] as String!, let text = messageData["text"] as String!, text.characters.count > 0 {
// 4
self.addMessage(withId: id, name: name, text: text)
// 5
self.finishReceivingMessage()
} else if let id = messageData["senderId"] as String!,
let photoURL = messageData["photoURL"] as String! { // 1
// 2
if let mediaItem = JSQPhotoMediaItem(maskAsOutgoing: id == self.senderId) {
// 3
self.addPhotoMessage(withId: id, key: snapshot.key, mediaItem: mediaItem)
// 4
if photoURL.hasPrefix("gs://") {
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: nil)
}
}
} else {
print("Error! Could not decode message data")
}
})
// We can also use the observer method to listen for
// changes to existing messages.
// We use this to be notified when a photo has been stored
// to the Firebase Storage, so we can update the message data
updatedMessageRefHandle = messageRef.observe(.childChanged, with: { (snapshot) in
let key = snapshot.key
let messageData = snapshot.value as! Dictionary<String, String> // 1
if let photoURL = messageData["photoURL"] as String! { // 2
// The photo has been updated.
if let mediaItem = self.photoMessageMap[key] { // 3
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: key) // 4
}
}
})
}
override func textViewDidChange(_ textView: UITextView) {
super.textViewDidChange(textView)
// If the text is not empty, the user is typing
isTyping = textView.text != ""
}
private func observeTyping() {
let typingIndicatorRef = rootRef.child("typingIndicator")
userIsTypingRef = typingIndicatorRef.child(senderId)
userIsTypingRef.onDisconnectRemoveValue()
usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqual(toValue: true)
// 1
usersTypingQuery.observe(.value) { (data: FIRDataSnapshot) in
// 2 You're the only one typing, don't show the indicator
if data.childrenCount == 1 && self.isTyping {
return
}
// 3 Are there others typing?
self.showTypingIndicator = data.childrenCount > 0
self.scrollToBottom(animated: true)
}
}
func sendPhotoMessage() -> String? {
let itemRef = messageRef.childByAutoId()
let messageItem = [
"photoURL": imageURLNotSetKey,
"senderId": senderId!,
]
itemRef.setValue(messageItem)
JSQSystemSoundPlayer.jsq_playMessageSentSound()
finishSendingMessage()
return itemRef.key
}
func setImageURL(_ url: String, forPhotoMessageWithKey key: String) {
let itemRef = messageRef.child(key)
itemRef.updateChildValues(["photoURL": url])
}
override func didPressAccessoryButton(_ sender: UIButton) {
let picker = UIImagePickerController()
picker.delegate = self as! UIImagePickerControllerDelegate & UINavigationControllerDelegate
if (UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera)) {
picker.sourceType = UIImagePickerControllerSourceType.camera
} else {
picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
}
present(picker, animated: true, completion:nil)
}
private func addPhotoMessage(withId id: String, key: String, mediaItem: JSQPhotoMediaItem) {
if let message = JSQMessage(senderId: id, displayName: "", media: mediaItem) {
messages.append(message)
if (mediaItem.image == nil) {
photoMessageMap[key] = mediaItem
}
collectionView.reloadData()
}
}
private func fetchImageDataAtURL(_ photoURL: String, forMediaItem mediaItem: JSQPhotoMediaItem, clearsPhotoMessageMapOnSuccessForKey key: String?) {
// 1
let storageRef = FIRStorage.storage().reference(forURL: photoURL)
// 2
storageRef.data(withMaxSize: INT64_MAX){ (data, error) in
if let error = error {
print("Error downloading image data: \(error)")
return
}
// 3
storageRef.metadata(completion: { (metadata, metadataErr) in
if let error = metadataErr {
print("Error downloading metadata: \(error)")
return
}
// 4
if (metadata?.contentType == "image") {
mediaItem.image = UIImage.init(data: data!)
} else {
mediaItem.image = UIImage.init(data: data!)
}
self.collectionView.reloadData()
// 5
guard key != nil else {
return
}
self.photoMessageMap.removeValue(forKey: key!)
})
}
}
}
extension SendMessageViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
picker.dismiss(animated: true, completion:nil)
// 1
if let photoReferenceUrl = info[UIImagePickerControllerReferenceURL] as? URL {
// Handle picking a Photo from the Photo Library
// 2
let assets = PHAsset.fetchAssets(withALAssetURLs: [photoReferenceUrl], options: nil)
let asset = assets.firstObject
// 3
if let key = sendPhotoMessage() {
// 4
asset?.requestContentEditingInput(with: nil, completionHandler: { (contentEditingInput, info) in
let imageFileURL = contentEditingInput?.fullSizeImageURL
// 5
let path = "\(FIRAuth.auth()?.currentUser?.uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\(photoReferenceUrl.lastPathComponent)"
// 6
self.storageRef.child(path).putFile(imageFileURL!, metadata: nil) { (metadata, error) in
if let error = error {
print("Error uploading photo: \(error.localizedDescription)")
return
}
// 7
self.setImageURL(self.storageRef.child((metadata?.path)!).description, forPhotoMessageWithKey: key)
}
})
}
} else {
// Handle picking a Photo from the Camera - TODO
// 1
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
// 2
if let key = sendPhotoMessage() {
// 3
let imageData = UIImageJPEGRepresentation(image, 1.0)
// 4
let imagePath = FIRAuth.auth()!.currentUser!.uid + "/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
// 5
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpeg"
// 6
storageRef.child(imagePath).put(imageData!, metadata: metadata) { (metadata, error) in
if let error = error {
print("Error uploading photo: \(error)")
return
}
// 7
self.setImageURL(self.storageRef.child((metadata?.path)!).description, forPhotoMessageWithKey: key)
}
}
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
}
Looks to me like this isn't a privacy issue as you state, it's simply that you're not clearing the data on your messages view controller when you load the new conversation.
Ultimately it really depends on how secure you want this to be; if you're happy having the private messages saved in memory, then don't destroy them until the user logs out — you can even keep multiple private conversations saved in a CoreData database. It's still relatively secure this way, and it's convenient for users and performant. If you prefer to destroy messages sooner, clear the data on viewDidDisappear, then checking in your prepareForSegue method that the data is again cleared. You could also destroy the entire messages controller each time you dismiss it, if storing a strong reference isn't what you want to do.
An example of this, as a storyboard:
App loads
User1 is logged in
User1 selects private messages
User1 has conversation with User2
User1 switches to a conversation with User3
[pseudo-code]
userDidChangeRecipient {
// destroy messages view controller
// or destroy Firebase array data and destroy the reference to the message/conversation ID
}
And each time you load the view controller:
prepareForSegue {
if strongRefToMessagesVC == nil {
// instantiate a new instance of vc from nib or scratch
// load the appropriate message/conversation ID
// load messages
}
}
More digging:
There's two possibilities here:
You're not destroying the view controller when you switch messages, and this tutorial expects you to. In that case, you need to look at when the segue ends or the user closes the messages view controller and either destroy it, or empty the array.
You're trying to write all the private messages into the same JSQMessage array. I notice in that view controller you have:
var messageRef = FIRDatabase.database().reference().child("messages")
Is that the database connection you're working with? Each private message conversation should have a unique reference ID so that they do not overlap, otherwise every user will load up the same set of messages from Firebase.
I am having a hard time trying to figure out, how I can change/update a specific part of my firebase database through swift. To give you an example of how my firebase database is structured, here you have a photo:
I am trying to update the likesForPost +1 everytime someone hits the like button that I have in my tableViewController. The important part is that every likesForPost should not be updates, just the one where the button is. I hope you understand my situation and that you can help me :-)
My struct
struct Sweet {
let key: String!
let content: String!
let addedByUser: String!
let profilePhoto: String!
var likesForPost: String!
let itemRef: FIRDatabaseReference?
init (content: String, addedByUser: String, profilePhoto: String!, likesForPost: String!, key: String = "") {
self.key = key
self.content = content
self.addedByUser = addedByUser
self.profilePhoto = profilePhoto
self.likesForPost = likesForPost
self.itemRef = nil
}
init (snapshot: FIRDataSnapshot) {
key = snapshot.key
itemRef = snapshot.ref
if let theFeedContent = snapshot.value!["content"] as? String {
content = theFeedContent
} else {
content = ""
}
if let feedUser = snapshot.value!["addedByUser"] as? String {
addedByUser = feedUser
} else {
addedByUser = ""
}
if let feedPhoto = snapshot.value!["profilePhoto"] as? String! {
profilePhoto = feedPhoto
} else {
profilePhoto = ""
}
if let feedLikes = snapshot.value!["likesForPost"] as? String! {
likesForPost = feedLikes
} else {
likesForPost = "0"
}
}
func toAnyObject() -> AnyObject {
return ["content":content, "addedByUser":addedByUser, "profilePhoto":profilePhoto!, "likesForPost":likesForPost]
}
}
My UITableViewController
import UIKit
import FirebaseDatabase
import FirebaseAuth
import FBSDKCoreKit
class feedTableViewController: UITableViewController {
#IBOutlet weak var loadingSpinner: UIActivityIndicatorView!
var facebookProfileUrl = ""
var dbRef: FIRDatabaseReference!
var updates = [Sweet]()
override func viewDidLoad() {
super.viewDidLoad()
loadingSpinner.startAnimating()
dbRef = FIRDatabase.database().reference().child("feed-items")
startObersvingDB()
}
func startObersvingDB() {
dbRef.observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
var newUpdates = [Sweet]()
for update in snapshot.children {
let updateObject = Sweet(snapshot: update as! FIRDataSnapshot)
newUpdates.append(updateObject)
}
self.updates = newUpdates
self.tableView.reloadData()
}) { (error: NSError) in
print(error.description)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func addToFeed(sender: AnyObject) {
let feedAlert = UIAlertController(title: "New update", message: "Enter your update", preferredStyle: .Alert)
feedAlert.addTextFieldWithConfigurationHandler { (textField:UITextField) in
textField.placeholder = "Your update"
}
feedAlert.addAction(UIAlertAction(title: "Send", style: .Default, handler: { (action:UIAlertAction) in
if let feedContent = feedAlert.textFields?.first?.text {
if let user = FIRAuth.auth()?.currentUser {
let name = user.displayName
//let photoUrl = user.photoURL
let accessToken = FBSDKAccessToken.currentAccessToken()
if(accessToken != nil) //should be != nil
{
let req = FBSDKGraphRequest(graphPath: "me", parameters: ["fields":"id"], tokenString: accessToken.tokenString, version: nil, HTTPMethod: "GET")
req.startWithCompletionHandler({ (connection, result, error : NSError!) -> Void in
if(error == nil)
{
let userId: String! = result.valueForKey("id") as? String!
let userID = userId
self.facebookProfileUrl = "http://graph.facebook.com/\(userID)/picture?type=large"
let likes = "0"
let feed = Sweet(content: feedContent, addedByUser: name!, profilePhoto: self.facebookProfileUrl, likesForPost: likes)
let feedRef = self.dbRef.child(feedContent.lowercaseString)
feedRef.setValue(feed.toAnyObject())
}
else
{
print("error \(error)")
}
})
}
// LAV FEEDCONTENT OM TIL OGSÅ AT MODTAGE PROFIL BILLEDE URL I STRING OG GIV SÅ facebookProfileUrl STRING LIGE HERUNDER I feed
} else {
// No user is signed in.
}
}
}))
self.presentViewController(feedAlert, animated: true, completion: nil)
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return updates.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:updateTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! updateTableViewCell
let update = updates[indexPath.row]
//cell.textLabel?.text = update.content
//cell.detailTextLabel?.text = update.addedByUser
cell.nameLabel.text = update.addedByUser
cell.updateLabel.text = update.content
cell.likesLabel.text = "\(update.likesForPost) hi-fives"
if update.profilePhoto! != "" {
if let url = NSURL(string: update.profilePhoto!) {
if let data = NSData(contentsOfURL: url) {
cell.picView.image = UIImage(data: data)
cell.picView.layer.cornerRadius = cell.picView.frame.size.width/2
cell.picView.clipsToBounds = true
}
}
} else {
print("Empty facebookProfileUrl")
}
loadingSpinner.stopAnimating()
return cell
}
}
Modify your struct to include one more variable (lets say let path : String!)that will include the value of the node key retrieved from your DB(megaTest or test).
Your Struct
struct Sweet {
let key: String!
let content: String!
let addedByUser: String!
let profilePhoto: String!
var likesForPost: String!
let itemRef: FIRDatabaseReference?
let path : String!
init (content: String, addedByUser: String, profilePhoto: String!, likesForPost: String!, key: String = "",dataPath : String!) {
self.key = key
self.content = content
self.addedByUser = addedByUser
self.profilePhoto = profilePhoto
self.likesForPost = likesForPost
self.itemRef = nil
self.path = dataPath
}
init (snapshot: FIRDataSnapshot) {
key = snapshot.key
itemRef = snapshot.ref
path = key
if let theFeedContent = snapshot.value!["content"] as? String {
content = theFeedContent
} else {
content = ""
}
if let feedUser = snapshot.value!["addedByUser"] as? String {
addedByUser = feedUser
} else {
addedByUser = ""
}
if let feedPhoto = snapshot.value!["profilePhoto"] as? String! {
profilePhoto = feedPhoto
} else {
profilePhoto = ""
}
if let feedLikes = snapshot.value!["likesForPost"] as? String! {
likesForPost = feedLikes
} else {
likesForPost = "0"
}
}
func toAnyObject() -> AnyObject {
return ["content":content, "addedByUser":addedByUser, "profilePhoto":profilePhoto!, "likesForPost":likesForPost,"pathInTheDB" : path]
}
}
In cellForIndexPath just add this
cell. pathDB = self.structArray![indexPath.row].path
Modify your customCell class like this
class customTableViewCell : UITableViewCell{
var pathDB : String! //megaTest or test
#IBAction func likeBtn(sender : UIButton!){
//Update like's
}
}
For updating the value you can use either runTransactionBlock:-
FIRDatabase.database().reference().child(pathDB).child("likesForPost").runTransactionBlock({ (likes: FIRMutableData) -> FIRTransactionResult in
// Set value and report transaction success
likes.value = likes.value as! Int + 1
return FIRTransactionResult.successWithValue(likes)
}) { (err, bl, snap) in
if let error = error {
print(error.localizedDescription)
}
}
Or observe that node with .observeSingleEventOfType, retrieve the snap and then update
let parentRef = FIRDatabase.database().reference().child(pathDB).child("likesForPost")
parentRef.observeSingleEventOfType(.Value,withBlock : {(snap) in
if let nOfLikes = snap.value as? Int{
parentRef.setValue(nOfLikes+1)
}
})