2017-05-17 92 views
0

我已經在我的iOS應用中實現了私人聊天。但是,它並不那麼私密。當我發送一條消息時,我打算髮送給一個人,應用中的每個人都可以看到它。我在這裏玩三個視圖控制器。 enter image description here如何在使用Firebase的ios中實現私人聊天

FirstViewController有一個用戶列表,當單擊單元格時,它將被查找到DetailedViewController。在這個viewController中,它只列出用戶點擊的細節。接下來,當我按下DetailedViewController中的組合按鈕時,目標是延續到MessageUserController。這是我卡住的地方。這是擴展到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 
} 

我假設發件人可以是用戶名,因爲它對用戶是唯一的。當我點擊一個用戶聊天時,它可以正常工作,但是當我點擊另一個用戶時,第一個用戶之間的聊天已經顯示在新用戶的聊天控制器中。

在第一個控制器中,用戶名如下傳遞:

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

整個視圖控制器:

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) 
    } 
} 
+0

我不確定自己是否正確理解,但看起來更像是在清除消息視圖控制器中顯示的數據時遇到問題,而不一定讓您的聊天數據保持私密? – brandonscript

+0

@brandonscript如果是這樣的話,我將如何去清除數據 – juelizabeth

回答

1

在我看來就像是你的國家,這是不是一個隱私問題,它只是你沒有清除你的消息的數據視圖控制器時加載新的談話。

最終它取決於你想要的安全性;如果您很高興將私人信息保存在內存中,那麼在用戶註銷之前不要銷燬它們 - 您甚至可以將多個私人對話保存在CoreData數據庫中。這種方式還是比較安全的,對用戶和表演者來說很方便。如果您希望更快地銷燬郵件,請清除viewDidDisappear上的數據,然後檢查您的prepareForSegue方法是否再次清除數據。如果存儲一個強引用不是你想要做的,你也可以在每次解除它時銷燬整個消息控制器。

這樣的一個例子,作爲一個情節串連圖板:

  1. 應用加載
  2. 用戶1
  3. 用戶1記錄選擇私人信息
  4. 用戶1具有User2的會話

  1. 用戶1切換到談話用戶3
  2. [僞代碼]

    userDidChangeRecipient { 
        // destroy messages view controller 
        // or destroy Firebase array data and destroy the reference to the message/conversation ID 
    } 
    

而每一次加載視圖控制器:

prepareForSegue { 
    if strongRefToMessagesVC == nil { 
     // instantiate a new instance of vc from nib or scratch 
     // load the appropriate message/conversation ID 
     // load messages 
    } 
} 

更多的挖掘:

有兩種可能性在這裏:

  1. 你不破壞視圖控制器,當你切換的消息,本教程期望你。在這種情況下,您需要查看segue何時結束或用戶關閉消息視圖控制器並將其銷燬或清空數組。

  2. 您正在嘗試將所有私人消息寫入同一個JSQMessage數組。我注意到你有這個視圖控制器:

    var messageRef = FIRDatabase.database().reference().child("messages") 
    

    是你正在使用的數據庫連接?每個私人消息對話應具有唯一的參考ID,以便它們不重疊,否則每個用戶都會從Firebase加載相同的消息集。

+0

如果我銷燬消息,用戶是否能夠回到那個對話? – juelizabeth

+0

我正在使用本教程(https://www.raywenderlich.com/140836/firebase-tutorial-real-time-chat-2)來實現聊天。我相信這個功能和你描述的一樣。 'DEINIT { 如果讓refHandle = newMessageRefHandle { messageRef.removeObserver(withHandle:refHandle) } 如果讓refHandle = updatedMessageRefHandle { MESSAGEREF。removeObserver(withHandle:refHandle) } }'但它並不真正工作 – juelizabeth

+0

我也只是第三個用戶,當我點擊一個用戶發送消息時,前兩個用戶的對話仍然存在,就像羣聊一樣。這絕對不是我想要的 – juelizabeth