sent message added twice in JSQMessageViewController - ios

I am using JSQMessageviewcontroller for creating a chat cliet using parse. I am sucessfull in achieving everything except "My sent message is being added twice in the jsqmessage array". Althuogh I have checked if JSQMessage array already contains that message, it should not be added, but the condition is false in case of a sent message.
Here is my code:
import UIKit
import JSQMessagesViewController
import Parse
class MessageViewController: JSQMessagesViewController {
var chatWith : String?
var chattingWith = false
let incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor(red: 10/255, green: 180/255, blue: 230/255, alpha: 1.0))
let outgoingBubble = JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImageWithColor(UIColor.lightGrayColor())
var messages = [JSQMessage]()
var updateTimer = NSTimer()
let updateDelay = 2.0
var jsqMessages : [JSQMessage] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.setup()
updateTimer = NSTimer.scheduledTimerWithTimeInterval(updateDelay, target: self, selector: "update", userInfo: nil, repeats: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidDisappear(animated: Bool) {
updateTimer.invalidate()
}
func reloadMessagesView() {
self.collectionView?.reloadData()
}
}
//MARK - Setup
extension MessageViewController {
func setup() {
self.senderId = PFUser.currentUser()?.objectId!
self.senderDisplayName = PFUser.currentUser()?.username!
}
}
//MARK - Data Source
extension MessageViewController {
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.messages.count
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
let data = self.messages[indexPath.row]
return data
}
override func collectionView(collectionView: JSQMessagesCollectionView!, didDeleteMessageAtIndexPath indexPath: NSIndexPath!) {
self.messages.removeAtIndex(indexPath.row)
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let data = messages[indexPath.row]
switch(data.senderId) {
case self.senderId:
return self.outgoingBubble
default:
return self.incomingBubble
}
}
override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
}
//MARK - Toolbar
extension MessageViewController {
override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
let message = JSQMessage(senderId: PFUser.currentUser()?.objectId!, senderDisplayName: PFUser.currentUser()?.username!, date: date, text: text)
print("sent messages = \(message)")
if !self.messages.contains(message) {
self.messages += [message]
}
self.sendMessageToParse(message , date: date)
self.finishSendingMessage()
}
override func didPressAccessoryButton(sender: UIButton!) {
}
}
//MARK - Syncano
extension MessageViewController {
func sendMessageToParse(message: JSQMessage , date : NSDate) {
let obj = PFObject(className: "Chat")
obj.setObject((PFUser.currentUser()?.username)!, forKey: "username")
obj.setObject(message.text!, forKey: "Text")
obj.setObject(chatWith!, forKey: "To")
obj.setObject(PFUser.currentUser()!, forKey: "user")
obj.setObject(date, forKey: "dateCreatedOn")
try! obj.save()
}
func update() {
let query = PFQuery(className: "Chat")
query.limit = 10
query.includeKey("user")
// query.whereKey("To", equalTo: chatWith!)
query.findObjectsInBackgroundWithBlock {
(results: [PFObject]?, error: NSError?) -> Void in
for i in results! {
if i.objectForKey("username") as? String == PFUser.currentUser()?.username! {
let message = self.jsqMessageFromParseMessage(i, senderId: (PFUser.currentUser()?.objectId!)!, senderDisplayName: i.objectForKey("username")! as! String)
print("received message : \(message)")
self.jsqMessageArray(message)
}else {
let message = self.jsqMessageFromParseMessage(i, senderId: i.objectForKey("To")! as! String, senderDisplayName: i.objectForKey("To")! as! String)
self.jsqMessageArray(message)
}
}
}
}
func jsqMessageFromParseMessage(message: PFObject , senderId : String , senderDisplayName : String) -> JSQMessage {
let jsqMessage = JSQMessage(senderId: senderId, senderDisplayName: senderDisplayName, date: message["dateCreatedOn"] as! NSDate, text: message["Text"] as! String)
return jsqMessage
}
func jsqMessageArray(jsqmessage : JSQMessage){
if self.messages.contains(jsqmessage) {
}else {
//
self.messages.append(jsqmessage)
self.finishReceivingMessage()
self.reloadMessagesView()
}
}
}
Will really appreciate if anyone can help. Thanks in advance.

There is a method that you can implement on JSQMessageData protocol called messageHash. It returns an Int and it is meant to be a unique id for each message. You should be able to use that to check and see if the message is the same or not.
You would need to create an object data class that adheres to the JSQMessageData protocol instead of using JSQMessage
class Message: JSQMessageData {
func senderId()-> String {
return //the result of currentUser()?.objectId
}
func senderDisplayName()-> String {
return //the result of currentUser()?.username!
}
func date()-> NSDate {
return // date
}
func isMediaMessage() -> Bool {
return false
}
func messageHash()-> String {
return //return a uniquely created id
}
func text()-> String {
return // text
}
}
You should be able to do this on PFUser, although I am not familiar enough with Parse to tell you if this is possible or not, but your really only re-mapping existing properties to be new names so it shouldn't be a problem

Related

TableView does not display content in the cells (Swift)

I created a UIViewController class with a tableView to show different places in the cells, i'm working with Alamofire and Google API (maps, places), the only problem is that when i run the project the cells are empty, this is my controller class:
import UIKit
class SelectClass: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var list : [QCategoryy] = [QCategoryy]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
self.title = "Categories"
list = NearbyPlaces.getCategories()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
list.sort() { $0.views > $1.views}
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func backTapp(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
#IBAction func doneTapp(_ sender: Any) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "CATEGORY_CELL"
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = list[indexPath.row].name
return cell
}
let nearbySearchSegueIdentifier = "goToMcourse"
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: nearbySearchSegueIdentifier, sender: list[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == nearbySearchSegueIdentifier {
guard let category = sender as? QCategoryy else {
return
}
if let vc = segue.destination as? CourseClass2 {
vc.category = category
}
}
}
}
extension QCategoryy {
private static let ketPrefix = "category-"
var views:Int {
get {
return UserDefaults.standard.integer(forKey: QCategoryy.ketPrefix + name)
}
}
func markView() {
UserDefaults.standard.set(views + 1, forKey: QCategoryy.ketPrefix + name)
}
}
and these are the two classes that work with it:
import Foundation
import UIKit
import CoreLocation
import Alamofire
class NearbyPlaces {
static func getCategories() -> [QCategoryy] {
let list:[QCategoryy] = ["Bakery", "Doctor", "School", "Taxi_stand", "Hair_care", "Restaurant", "Pharmacy", "Atm", "Gym", "Store", "Spa"]
return list
}
static let searchApiHost = "https://maps.googleapis.com/maps/api/place/nearbysearch/json"
static let googlePhotosHost = "https://maps.googleapis.com/maps/api/place/photo"
static func getNearbyPlaces(by category:String, coordinates:CLLocationCoordinate2D, radius:Int, token: String?, completion: #escaping (QNearbyPlacesResponse?, Error?) -> Void) {
var params : [String : Any]
if let t = token {
params = [
"key" : AppDelegate.googlePlacesAPIKey,
"pagetoken" : t,
]
} else {
params = [
"key" : AppDelegate.googlePlacesAPIKey,
"radius" : radius,
"location" : "\(coordinates.latitude),\(coordinates.longitude)",
"type" : category.lowercased()
]
}
Alamofire.request(searchApiHost, parameters: params, encoding: URLEncoding(destination: .queryString)).responseJSON { response in
if let error = response.error {
completion(nil, error)
}
if let response = QNearbyPlacesResponse(dic : response.result.value as? [String : Any]) {
completion(response, nil)
}
else {
completion(nil, QNearbyPlacesResponseError.noParsingDone)
}
}
}
static func googlePhotoURL(photoReference:String, maxWidth:Int) -> URL? {
return URL.init(string: "\(googlePhotosHost)?maxwidth=\(maxWidth)&key=\(AppDelegate.googlePlacesAPIKey)&photoreference=\(photoReference)")
}
}
enum QNearbyPlacesResponseError : Error {
case noParsingDone
}
struct QNearbyPlacesResponse {
var nextPageToken: String?
var status: String = "NOK"
var places: [QPlace]?
init?(dic:[String : Any]?) {
nextPageToken = dic?["next_page_token"] as? String
if let status = dic?["status"] as? String {
self.status = status
}
if let results = dic?["results"] as? [[String : Any]]{
var places = [QPlace]()
for place in results {
places.append(QPlace.init(placeInfo: place))
}
self.places = places
}
}
func canLoadMore() -> Bool {
if status == "OK" && nextPageToken != nil && nextPageToken?.characters.count ?? 0 > 0 {
return true
}
return false
}
}
and
struct QCategoryy {
var name:String
init(name:String) {
self.name = name
}
}
extension QCategoryy: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.name = value
}
init(unicodeScalarLiteral value: String) {
self.init(name: value)
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(name: value)
}
}
it seems to me that it's all right, i can not understand why when i go into my uiviewcontroller the tableView has all the empty cells, here there is also a screen of what i see when running the project
hope someone can find the issue
Try with:
tableView.dataSource = self
Try adding:
tableView.dataSource = self
tableView.delegate = self`
to your viewDidLoad()

Firebase chat app crashes when clicking on table cell

I downloaded a demo chat application that runs perfectly but when I implement it into my own app it crashes and the code is exactly the same. The app gives you the ability to create a chat room and my app works up to this point but when you click on the chat room name that now appears on the table I get the following error:
Assertion failure in -[Irish_League_Grounds.ChatViewController viewWillAppear:], /Users/ryanball/Desktop/Irish League
Grounds/Pods/JSQMessagesViewController/JSQMessagesViewController/Controllers/JSQMessagesViewController.m:277
2017-05-17 17:32:55.815 Irish League Grounds[20456:681491]
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid parameter not
satisfying: self.senderDisplayName != nil'
Here is my code for the two view controllers I'm segueing between:
import UIKit
import Firebase
enum Section: Int {
case createNewChannelSection = 0
case currentChannelsSection
}
class ChannelListViewController: UITableViewController {
// MARK: Properties
var senderDisplayName: String?
var newChannelTextField: UITextField?
private var channelRefHandle: FIRDatabaseHandle?
private var channels: [Channel] = []
private lazy var channelRef: FIRDatabaseReference = FIRDatabase.database().reference().child("channels")
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
title = "RW RIC"
observeChannels()
}
deinit {
if let refHandle = channelRefHandle {
channelRef.removeObserver(withHandle: refHandle)
}
}
// MARK :Actions
#IBAction func createChannel(_ sender: AnyObject) {
if let name = newChannelTextField?.text {
let newChannelRef = channelRef.childByAutoId()
let channelItem = [
"name": name
]
newChannelRef.setValue(channelItem)
}
}
// MARK: Firebase related methods
private func observeChannels() {
// We can use the observe method to listen for new
// channels being written to the Firebase DB
channelRefHandle = channelRef.observe(.childAdded, with: { (snapshot) -> Void in
let channelData = snapshot.value as! Dictionary<String, AnyObject>
let id = snapshot.key
if let name = channelData["name"] as! String!, name.characters.count > 0 {
self.channels.append(Channel(id: id, name: name))
self.tableView.reloadData()
} else {
print("Error! Could not decode channel data")
}
})
}
// MARK: Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let channel = sender as? Channel {
let chatVc = segue.destination as! ChatViewController
chatVc.senderDisplayName = senderDisplayName
chatVc.channel = channel
chatVc.channelRef = channelRef.child(channel.id)
}
}
// MARK: UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let currentSection: Section = Section(rawValue: section) {
switch currentSection {
case .createNewChannelSection:
return 1
case .currentChannelsSection:
return channels.count
}
} else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let reuseIdentifier = (indexPath as NSIndexPath).section == Section.createNewChannelSection.rawValue ? "NewChannel" : "ExistingChannel"
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
if (indexPath as NSIndexPath).section == Section.createNewChannelSection.rawValue {
if let createNewChannelCell = cell as? CreateChannelCell {
newChannelTextField = createNewChannelCell.newChannelNameField
}
} else if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
cell.textLabel?.text = channels[(indexPath as NSIndexPath).row].name
}
return cell
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
let channel = channels[(indexPath as NSIndexPath).row]
self.performSegue(withIdentifier: "ShowChannel", sender: channel)
}
}
}
Here is the second view controller:
import UIKit
import Photos
import Firebase
import JSQMessagesViewController
final class ChatViewController: JSQMessagesViewController {
// MARK: Properties
private let imageURLNotSetKey = "NOTSET"
var channelRef: FIRDatabaseReference?
private lazy var messageRef: FIRDatabaseReference = self.channelRef!.child("messages")
fileprivate lazy var storageRef: FIRStorageReference = FIRStorage.storage().reference(forURL: "gs://chatchat-871d0.appspot.com")
private lazy var userIsTypingRef: FIRDatabaseReference = self.channelRef!.child("typingIndicator").child(self.senderId)
private lazy var usersTypingQuery: FIRDatabaseQuery = self.channelRef!.child("typingIndicator").queryOrderedByValue().queryEqual(toValue: true)
private var newMessageRefHandle: FIRDatabaseHandle?
private var updatedMessageRefHandle: FIRDatabaseHandle?
private var messages: [JSQMessage] = []
private var photoMessageMap = [String: JSQPhotoMediaItem]()
private var localTyping = false
var channel: Channel? {
didSet {
title = channel?.name
}
}
var isTyping: Bool {
get {
return localTyping
}
set {
localTyping = newValue
userIsTypingRef.setValue(newValue)
}
}
lazy var outgoingBubbleImageView: JSQMessagesBubbleImage = self.setupOutgoingBubble()
lazy var incomingBubbleImageView: JSQMessagesBubbleImage = self.setupIncomingBubble()
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.senderId = FIRAuth.auth()?.currentUser?.uid
observeMessages()
// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
}
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)
}
}
// MARK: Collection view data source (and related) methods
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
}
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: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId { // 1
cell.textView?.textColor = UIColor.white // 2
} else {
cell.textView?.textColor = UIColor.black // 3
}
return cell
}
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: JSQMessagesCollectionView?, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath!) -> NSAttributedString? {
let message = messages[indexPath.item]
switch message.senderId {
case senderId:
return nil
default:
guard let senderDisplayName = message.senderDisplayName else {
assertionFailure()
return nil
}
return NSAttributedString(string: senderDisplayName)
}
}
// MARK: Firebase related methods
private func observeMessages() {
messageRef = channelRef!.child("messages")
let messageQuery = messageRef.queryLimited(toLast:25)
// 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
let messageData = snapshot.value as! Dictionary<String, String>
if let id = messageData["senderId"] as String!, let name = messageData["senderName"] as String!, let text = messageData["text"] as String!, text.characters.count > 0 {
self.addMessage(withId: id, name: name, text: text)
self.finishReceivingMessage()
} else if let id = messageData["senderId"] as String!, let photoURL = messageData["photoURL"] as String! {
if let mediaItem = JSQPhotoMediaItem(maskAsOutgoing: id == self.senderId) {
self.addPhotoMessage(withId: id, key: snapshot.key, mediaItem: mediaItem)
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>
if let photoURL = messageData["photoURL"] as String! {
// The photo has been updated.
if let mediaItem = self.photoMessageMap[key] {
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: key)
}
}
})
}
private func fetchImageDataAtURL(_ photoURL: String, forMediaItem mediaItem: JSQPhotoMediaItem, clearsPhotoMessageMapOnSuccessForKey key: String?) {
let storageRef = FIRStorage.storage().reference(forURL: photoURL)
storageRef.data(withMaxSize: INT64_MAX){ (data, error) in
if let error = error {
print("Error downloading image data: \(error)")
return
}
storageRef.metadata(completion: { (metadata, metadataErr) in
if let error = metadataErr {
print("Error downloading metadata: \(error)")
return
}
if (metadata?.contentType == "image/gif") {
mediaItem.image = UIImage.gifWithData(data!)
} else {
mediaItem.image = UIImage.init(data: data!)
}
self.collectionView.reloadData()
guard key != nil else {
return
}
self.photoMessageMap.removeValue(forKey: key!)
})
}
}
private func observeTyping() {
let typingIndicatorRef = channelRef!.child("typingIndicator")
userIsTypingRef = typingIndicatorRef.child(senderId)
userIsTypingRef.onDisconnectRemoveValue()
usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqual(toValue: true)
usersTypingQuery.observe(.value) { (data: FIRDataSnapshot) in
// You're the only typing, don't show the indicator
if data.childrenCount == 1 && self.isTyping {
return
}
// Are there others typing?
self.showTypingIndicator = data.childrenCount > 0
self.scrollToBottom(animated: true)
}
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
// 1
let itemRef = messageRef.childByAutoId()
// 2
let messageItem = [
"senderId": senderId!,
"senderName": senderDisplayName!,
"text": text!,
]
// 3
itemRef.setValue(messageItem)
// 4
JSQSystemSoundPlayer.jsq_playMessageSentSound()
// 5
finishSendingMessage()
isTyping = false
}
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])
}
// MARK: UI and User Interaction
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 didPressAccessoryButton(_ sender: UIButton) {
let picker = UIImagePickerController()
picker.delegate = self
if (UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera)) {
picker.sourceType = UIImagePickerControllerSourceType.camera
} else {
picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
}
present(picker, animated: true, completion:nil)
}
private func addMessage(withId id: String, name: String, text: String) {
if let message = JSQMessage(senderId: id, displayName: name, text: text) {
messages.append(message)
}
}
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()
}
}
// MARK: UITextViewDelegate methods
override func textViewDidChange(_ textView: UITextView) {
super.textViewDidChange(textView)
// If the text is not empty, the user is typing
isTyping = textView.text != ""
}
}
// MARK: Image Picker Delegate
extension ChatViewController: 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
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
}
The problem lies with this section of your code:
guard let senderDisplayName = message.senderDisplayName else {
assertionFailure()
return nil
}
assertionFailure() must link to another function that runs assert(false) or some equivalent to that. assert(_) is meant to verify that a particular parameter or comparison returns true, or if it does not, it will crash the app. The app will not crash if it is a production build (like those on the App Store) because asserts are meant for debugging purposes.
Basically, the guard statement is necessary to verify that message.senderDisplayName is unwrappable to some value (not nil). If message.senderDisplayName is nil, then there is no point in running the code below the guard and the contents of the guard should be run instead. assertionFailure() will crash the app during testing and during production it will be ignored. When it is ignored, nil will be returned for the function and it will continue on as nothing happened.

JSQMessagesViewController and Firebase

I am using Firebase and JSQMessagesViewcontroller. I add some message to test but didn't show up. Can anyone help to see what I have missed? Thanks!
My Code:
import UIKit
import Firebase
import JSQMessagesViewController
class ChatViewController: JSQMessagesViewController {
var messages = [JSQMessage]()
lazy var outgoingBubbleImageView:JSQMessagesBubbleImage = self.setupOutgoingBubble()
lazy var incomingBubbleimageView:JSQMessagesBubbleImage = self.setupIncomingBubble()
override func viewDidLoad() {
super.viewDidLoad()
self.senderDisplayName = "cindy"
self.senderId = FIRAuth.auth()?.currentUser?.uid
print("sender id is: \(self.senderId)")
}
override func viewDidAppear(_ animated: Bool) {
addMessage(withId: "john", name: "doo", text: "hello, I am here")
addMessage(withId: senderId, name: "Me", text: "how are you")
addMessage(withId: senderId, name: "Me", text: "i am earlier than you.")
// I added finishReceivingMessage() here, but the app crashed.
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item]
if message.senderId == senderId {
return outgoingBubbleImageView
} else {
return incomingBubbleimageView
}
}
func addMessage(withId id:String, name: String, text:String) {
if let message = JSQMessage(senderId: id, displayName: name, text: text) {
self.messages.append(message)
}
}
func setupOutgoingBubble() -> JSQMessagesBubbleImage {
let bubbleImagesFactory = JSQMessagesBubbleImageFactory()
return (bubbleImagesFactory?.outgoingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue()))!
}
func setupIncomingBubble() -> JSQMessagesBubbleImage {
let bubbleImagesFactory = JSQMessagesBubbleImageFactory()
return (bubbleImagesFactory?.incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleLightGray()))!
}
}
So for starters I would suggest that you go and checkout the Develop branch of JSQMessagesVeiwController and pull up the SwiftExample contained within. This is the latest version and has been updated for swift3. Then I would isolate your issues. Since you are actually makeing the messages locally and not leveraging firebase I would Comment that out.
Then we can make your fake conversation like they do in the DemoConversation.swift file
let message = JSQMessage(senderId: "john", displayName: "doo", text: "hello, I am here")
let message2 = JSQMessage(senderId: "asdf", displayName: "Me", text: "how are you")
let message3 = JSQMessage(senderId: "asdf", displayName: "Me", text: "i am earlier than you.")
func makeNormalConversation() -> [JSQMessage] {
conversation = [message, message2, message3]
return conversation
}
Then you just need to make sure you have everything setup for you view and set your messages to makeNormalConversation()
Then for your ChatViewController the minimum amount of code is this:
//
// ChatViewController.swift
// SwiftExample
//
// Created by Dan Leonard on 5/11/16.
// Copyright © 2016 MacMeDan. All rights reserved.
//
import UIKit
import JSQMessagesViewController
class ChatViewController: JSQMessagesViewController {
var messages = [JSQMessage]()
let defaults = UserDefaults.standard
var conversation: Conversation = makeConversation() // Here is where your messages are added.
var incomingBubble: JSQMessagesBubbleImage!
var outgoingBubble: JSQMessagesBubbleImage!
fileprivate var displayName: String!
override func viewDidLoad() {
super.viewDidLoad()
incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue())
outgoingBubble = JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImage(with: UIColor.lightGray)
collectionView?.collectionViewLayout.incomingAvatarViewSize = .zero
collectionView?.collectionViewLayout.outgoingAvatarViewSize = .zero
automaticallyScrollsToMostRecentMessage = true
self.collectionView?.reloadData()
self.collectionView?.layoutIfNeeded()
}
// MARK: JSQMessagesViewController method overrides
override func didPressSend(_ button: UIButton, withMessageText text: String, senderId: String, senderDisplayName: String, date: Date) {
let message = JSQMessage(senderId: senderId, senderDisplayName: senderDisplayName, date: date, text: text)
self.messages.append(message)
self.finishSendingMessage(animated: true)
}
// MARK: JSQMessages CollectionView DataSource
override func senderId() -> String {
return "Cindy"
}
override func senderDisplayName() -> String {
return "Cindy Lue"
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, messageDataForItemAt indexPath: IndexPath) -> JSQMessageData {
return messages[indexPath.item]
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, messageBubbleImageDataForItemAt indexPath: IndexPath) -> JSQMessageBubbleImageDataSource {
return messages[indexPath.item].senderId == self.senderId() ? outgoingBubble : incomingBubble
}
}
Check out the example project on the develop branch for more details and let me know if you have more questions?
Then from this point you should add Firebase. You were on the right path for the senderID just make sure you actually get a unique id for the user that has logged in and then make sure it is the same ID for the messages sent by that user.
Good luck 🖖🏽
You must add finishReceivingMessage() after adding all of the example messages. This is sort of like a tableView.reloadData(), except for the JSQMessagesViewController. It also scrolls to newest message for you.

Firebase & JSQMessagesViewController One to one chat in Ios Swift 2

So i'm trying to implement one to one chat system in the app that am building and have been having some trouble. This is what i have so far.
Here is my code
Please help me i would also really appreciate any examples. Thank you
`
import UIKit
import JSQMessagesViewController
import Firebase
class MessagesViewController: JSQMessagesViewController {
var messages = [Message]()
var userImage = ""
var toUser: String!
var toUserImageUrl: String!
var avatars = Dictionary<String, UIImage>()
var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!
var senderImageUrl: String!
var batchMessages = true
// var ref: Firebase!
// *** STEP 1: STORE FIREBASE REFERENCES
var messagesRef: FIRDatabaseReference!
private func setupBubbles() {
let factory = JSQMessagesBubbleImageFactory()
outgoingBubbleImageView = factory.outgoingMessagesBubbleImageWithColor(
UIColor.jsq_messageBubbleBlueColor())
incomingBubbleImageView = factory.incomingMessagesBubbleImageWithColor(
UIColor.jsq_messageBubbleLightGrayColor())
}
override func viewDidLoad() {
super.viewDidLoad()
print(toUser)
inputToolbar.contentView.leftBarButtonItem = nil
automaticallyScrollsToMostRecentMessage = true
// self.collectionView.backgroundColor = UIColor.blackColor()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "< Back", style: UIBarButtonItemStyle.Bordered, target: self, action: #selector(MessagesViewController.back(_:)))
self.navigationItem.leftBarButtonItem = newBackButton;
self.navigationController?.navigationItem.leftBarButtonItem?.tintColor = UIColor.whiteColor()
senderId = (senderId != nil) ? senderId : "Anonymous"
if let urlString = userImage as? NSString {
setupAvatarImage(senderId, imageUrl: senderImageUrl, incoming: false)
senderImageUrl = urlString as String
} else {
setupAvatarColor(senderDisplayName, incoming: false)
senderImageUrl = ""
}
senderId = Constants.currentUser
self.title = toUser
// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero
setupBubbles()
setupFirebase()
}
func back(sender: UIBarButtonItem) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func setupFirebase() {
// *** STEP 2: SETUP FIREBASE
messagesRef = FIRDatabase.database().reference()
// *** STEP 4: RECEIVE MESSAGES FROM FIREBASE (limited to latest 25 messages)
messagesRef.observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
let text = snapshot.value!["text"] as? String
let sender = snapshot.value!["from"] as? String
let toUsername = snapshot.value!["toUser"] as? String
let senderimageUrl = snapshot.value!["senderimageUrl"] as? String
let toUserImageUrl = snapshot.value!["toUserimageUrl"] as? String
let message = Message(text: text, sender: sender, toUsername: toUsername, senderimageUrl: senderimageUrl, toUserimageUrl: toUserImageUrl)
self.messages.append(message)
self.finishReceivingMessage()
})
}
func sendMessage(text: String!, sender: String!, to: String!) {
// *** STEP 3: ADD A MESSAGE TO FIREBASE
messagesRef.childByAutoId().setValue([
"text":text,
"toUser":toUser,
"from":self.senderId,
"senderimageUrl":senderImageUrl,
"toUserimageUrl":toUserImageUrl
])
}
func tempSendMessage(text: String!, sender: String!, to: String!) {
let message = Message(text: text, sender: sender, toUsername: to, senderimageUrl: senderImageUrl, toUserimageUrl: toUserImageUrl)
messages.append(message)
}
func setupAvatarImage(name: String, imageUrl: String?, incoming: Bool) {
if let stringUrl = imageUrl {
if let url = NSURL(string: stringUrl) {
if let data = NSData(contentsOfURL: url) {
let image = UIImage(data: data)
let diameter = incoming ? UInt(collectionView.collectionViewLayout.incomingAvatarViewSize.width) : UInt(collectionView.collectionViewLayout.outgoingAvatarViewSize.width)
let avatarImage = JSQMessagesAvatarImageFactory.avatarImageWithImage(image, diameter: diameter).avatarImage
avatars[name] = avatarImage
return
}
}
}
// At some point, we failed at getting the image (probably broken URL), so default to avatarColor
setupAvatarColor(name, incoming: incoming)
}
func setupAvatarColor(name: String, incoming: Bool) {
let diameter = incoming ? UInt(collectionView.collectionViewLayout.incomingAvatarViewSize.width) : UInt(collectionView.collectionViewLayout.outgoingAvatarViewSize.width)
let rgbValue = name.hash
let r = CGFloat(Float((rgbValue & 0xFF0000) >> 16)/255.0)
let g = CGFloat(Float((rgbValue & 0xFF00) >> 8)/255.0)
let b = CGFloat(Float(rgbValue & 0xFF)/255.0)
let color = UIColor(red: r, green: g, blue: b, alpha: 0.5)
let nameLength = name.characters.count
let initials : String? = name.substringToIndex(toUser.startIndex.advancedBy(min(3, nameLength)))
let userImage = JSQMessagesAvatarImageFactory.avatarImageWithUserInitials(initials, backgroundColor: color, textColor: UIColor.blackColor(), font: UIFont.systemFontOfSize(CGFloat(13)), diameter: diameter).avatarImage
avatars[name] = userImage
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
collectionView.collectionViewLayout.springinessEnabled = true
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
// if ref != nil {
// ref.unauth()
// }
}
// ACTIONS
func receivedMessagePressed(sender: UIBarButtonItem) {
// Simulate reciving message
showTypingIndicator = !showTypingIndicator
scrollToBottomAnimated(true)
}
override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!){
JSQSystemSoundPlayer.jsq_playMessageSentSound()
sendMessage(text, sender: senderId, to: toUser)
finishSendingMessage()
}
override func didPressAccessoryButton(sender: UIButton!) {
print("Camera pressed!")
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(collectionView: JSQMessagesCollectionView!,
messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.senderId() == self.senderId{ // 2
return outgoingBubbleImageView
} else { // 3
return incomingBubbleImageView
}
}
override func collectionView(collectionView: JSQMessagesCollectionView!,
avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
func collectionView(collectionView: JSQMessagesCollectionView!, bubbleImageViewForItemAtIndexPath indexPath: NSIndexPath!) -> UIImageView! {
let message = messages[indexPath.item]
if message.senderId() == self.senderId {
return UIImageView(image: outgoingBubbleImageView.messageBubbleImage, highlightedImage: outgoingBubbleImageView.messageBubbleHighlightedImage)
}
return UIImageView(image: incomingBubbleImageView.messageBubbleImage, highlightedImage: incomingBubbleImageView.messageBubbleHighlightedImage)
}
func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageViewForItemAtIndexPath indexPath: NSIndexPath!) -> UIImageView! {
let message = messages[indexPath.item]
if let avatar = avatars[message.senderId()] {
return UIImageView(image: avatar)
} else {
setupAvatarImage(message.senderId(), imageUrl: message.senderimageUrl(), incoming: true)
return UIImageView(image:avatars[message.senderId()])
}
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId() == self.senderId {
cell.textView.textColor = UIColor.whiteColor()
} else {
cell.textView.textColor = UIColor.blackColor()
}
let attributes : [String:AnyObject] = [NSForegroundColorAttributeName:cell.textView.textColor!, NSUnderlineStyleAttributeName: 1]
cell.textView.linkTextAttributes = attributes
// cell.textView.linkTextAttributes = [NSForegroundColorAttributeName: cell.textView.textColor,
// NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle]
return cell
}
// View usernames above bubbles
override func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAtIndexPath indexPath: NSIndexPath!) -> NSAttributedString! {
let message = messages[indexPath.item];
// Sent by me, skip
if message.senderId() == self.senderId {
return nil;
}
// Same as previous sender, skip
if indexPath.item > 0 {
let previousMessage = messages[indexPath.item - 1];
if previousMessage.senderId() == message.senderId() {
return nil;
}
}
return NSAttributedString(string:message.senderId())
}
override func collectionView(collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
let message = messages[indexPath.item]
// Sent by me, skip
if message.senderId() == self.senderId {
return CGFloat(0.0);
}
// Same as previous sender, skip
if indexPath.item > 0 {
let previousMessage = messages[indexPath.item - 1];
if previousMessage.senderId() == message.senderId() {
return CGFloat(0.0);
}
}
return kJSQMessagesCollectionViewCellLabelHeightDefault
}
}
`
So I would suggest checking out the
Swift Example can be found in the SwiftExample folder just open the SwiftExample.xcworkspace.
https://github.com/jessesquires/JSQMessagesViewController
it is apart of JSQMessagesViewController and is currently maintained.Also has an example of both a group and 1on1 conversation. That should help you to accomplish private conversations. But I think you are missing a key component which is your conversations view. That is where you would initiate a conversation. Then you should have an endpoint on your firebase backend that you can hit for all the conversations a user has had previously. That way they can open them again.

reason: '-[JSQMessagesCollectionViewFlowLayoutInvalidationContext invalidateFlowLayoutMessagesCache]: unrecognized selector sent to instance

so let me just start by saying i am new to swift and obj-c programming so please forgive me if i ask stupid question so i am trying to make a chat app with jsqmessageviewcontroller library but i keep getting this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[JSQMessagesCollectionViewFlowLayoutInvalidationContext invalidateFlowLayoutMessagesCache]: unrecognized selector sent to instance
here is my code:
//
// MessagesViewController.swift
// ChatBiz
//
// Created by Afnan Mirza on 6/3/16.
// Copyright © 2016 Afnan Mirza. All rights reserved.
//
import UIKit
import Foundation
import JSQMessagesViewController
import Firebase
class MessagesViewController: JSQMessagesViewController {
var user: FAuthData?
var messages = [Message]()
var avatars = Dictionary<String, UIImage>()
var senderImageUrl: String!
var batchMessages = true
var ref: Firebase!
var to = String()
var username = NSUserDefaults.standardUserDefaults().stringForKey("username")!
var sender = String()
let bubbles = JSQMessagesBubbleImageFactory()
var outgoingBubbleImageView: JSQMessagesBubbleImage {
return bubbles.outgoingMessagesBubbleImageWithColor(UIColor.jsq_messageBubbleLightGrayColor())
}
var incomingBubbleImageView: JSQMessagesBubbleImage {
return bubbles.incomingMessagesBubbleImageWithColor(UIColor.jsq_messageBubbleGreenColor())
}
// *** STEP 1: STORE FIREBASE REFERENCES
var messagesRef: Firebase!
func setupFirebase() {
// *** STEP 2: SETUP FIREBASE
messagesRef = Firebase(url: "https://chatbiz.firebaseio.com/messages/\(username)")
// *** STEP 4: RECEIVE MESSAGES FROM FIREBASE (limited to latest 25 messages)
messagesRef.queryLimitedToNumberOfChildren(25).observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) in
let text = snapshot.value["message"] as? String
self.sender = (snapshot.value["sender"] as? String)!
let imageUrl = snapshot.value["imageUrl"] as? String
let message = Message(text: text, sender: self.sender, imageUrl: imageUrl)
self.messages.append(message)
self.finishReceivingMessage()
})
}
func sendMessage(text: String!, sender: String!) {
// *** STEP 3: ADD A MESSAGE TO FIREBASE
messagesRef.childByAutoId().setValue([
"message":text,
"sender":sender,
"to":to,
"imageUrl":senderImageUrl
])
}
func tempSendMessage(text: String!, sender: String!) {
let message = Message(text: text, sender: sender, imageUrl: senderImageUrl)
messages.append(message)
}
func setupAvatarImage(name: String, imageUrl: String?, incoming: Bool) {
if let stringUrl = imageUrl {
if let url = NSURL(string: stringUrl) {
if let data = NSData(contentsOfURL: url) {
let image = UIImage(data: data)
let diameter = incoming ? UInt(collectionView.collectionViewLayout.incomingAvatarViewSize.width) : UInt(collectionView.collectionViewLayout.outgoingAvatarViewSize.width)
let avatarImage = JSQMessagesAvatarImageFactory.avatarImageWithImage(image, diameter: diameter)
// avatars[name] = avatarImage
return
}
}
}
// At some point, we failed at getting the image (probably broken URL), so default to avatarColor
setupAvatarColor(name, incoming: incoming)
}
func setupAvatarColor(name: String, incoming: Bool) {
let diameter = incoming ? UInt(collectionView.collectionViewLayout.incomingAvatarViewSize.width) : UInt(collectionView.collectionViewLayout.outgoingAvatarViewSize.width)
let rgbValue = name.hash
let r = CGFloat(Float((rgbValue & 0xFF0000) >> 16)/255.0)
let g = CGFloat(Float((rgbValue & 0xFF00) >> 8)/255.0)
let b = CGFloat(Float(rgbValue & 0xFF)/255.0)
let color = UIColor(red: r, green: g, blue: b, alpha: 0.5)
let nameLength = name.characters.count
let initials : String? = name.substringToIndex(sender.startIndex.advancedBy(min(3, nameLength)))
let userImage = JSQMessagesAvatarImageFactory.avatarImageWithUserInitials(initials, backgroundColor: color, textColor: UIColor.blackColor(), font: UIFont.systemFontOfSize(CGFloat(13)), diameter: diameter)
// avatars[name] = userImage
}
override func viewDidLoad() {
super.viewDidLoad()
print(to)
inputToolbar.contentView.leftBarButtonItem = nil
navigationController?.navigationBar.topItem?.title = "Logout"
sender = (sender != "") ? sender : "Anonymous"
let profileImageUrl = user?.providerData["cachedUserProfile"]?["profile_image_url_https"] as? NSString
if let urlString = profileImageUrl {
setupAvatarImage(sender, imageUrl: urlString as String, incoming: false)
senderImageUrl = urlString as String
} else {
setupAvatarColor(sender, incoming: false)
senderImageUrl = ""
}
setupFirebase()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
collectionView.collectionViewLayout.springinessEnabled = true
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if ref != nil {
ref.unauth()
}
}
// ACTIONS
func receivedMessagePressed(sender: UIBarButtonItem) {
// Simulate reciving message
showTypingIndicator = !showTypingIndicator
scrollToBottomAnimated(true)
}
func didPressSendButton(button: UIButton!, withMessageText text: String!, sender: String!, date: NSDate!) {
JSQSystemSoundPlayer.jsq_playMessageSentSound()
sendMessage(text, sender: sender)
finishSendingMessage()
}
override func didPressAccessoryButton(sender: UIButton!) {
print("Camera pressed!")
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
return messages[indexPath.item] as! JSQMessageData
}
func collectionView(collectionView: JSQMessagesCollectionView!, bubbleImageViewForItemAtIndexPath indexPath: NSIndexPath!) -> UIImageView! {
let message = messages[indexPath.item]
if message.sender() == sender {
return UIImageView(image: outgoingBubbleImageView.messageBubbleImage, highlightedImage: outgoingBubbleImageView.messageBubbleHighlightedImage)
}
return UIImageView(image: incomingBubbleImageView.messageBubbleImage, highlightedImage: incomingBubbleImageView.messageBubbleHighlightedImage)
}
func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageViewForItemAtIndexPath indexPath: NSIndexPath!) -> UIImageView! {
let message = messages[indexPath.item]
if let avatar = avatars[message.sender()!] {
return UIImageView(image: avatar)
} else {
setupAvatarImage(message.sender()!, imageUrl: message.imageUrl(), incoming: true)
return UIImageView(image:avatars[message.sender()!])
}
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.sender() == sender {
cell.textView.textColor = UIColor.blackColor()
} else {
cell.textView.textColor = UIColor.whiteColor()
}
let attributes : [String:AnyObject] = [NSForegroundColorAttributeName:cell.textView.textColor!, NSUnderlineStyleAttributeName: 1]
cell.textView.linkTextAttributes = attributes
// cell.textView.linkTextAttributes = [NSForegroundColorAttributeName: cell.textView.textColor,
// NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle]
return cell
}
// View usernames above bubbles
override func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAtIndexPath indexPath: NSIndexPath!) -> NSAttributedString! {
let message = messages[indexPath.item];
// Sent by me, skip
if message.sender() == sender {
return nil;
}
// Same as previous sender, skip
if indexPath.item > 0 {
let previousMessage = messages[indexPath.item - 1];
if previousMessage.sender() == message.sender() {
return nil;
}
}
return NSAttributedString(string:message.sender())
}
override func collectionView(collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
let message = messages[indexPath.item]
// Sent by me, skip
if message.sender() == sender {
return CGFloat(0.0);
}
// Same as previous sender, skip
if indexPath.item > 0 {
let previousMessage = messages[indexPath.item - 1];
if previousMessage.sender() == message.sender() {
return CGFloat(0.0);
}
}
return kJSQMessagesCollectionViewCellLabelHeightDefault
}
}
Sorry for the late reply, but the reason for your crash is that "springinessEnabled" is still in development, so try to set it as false.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
collectionView.collectionViewLayout.springinessEnabled = false // Changed to false
}
For more info see this link

Resources