NSInvalidArgumentException with using JSQMessagesViewController - ios

I'm new to swift :D, and developing a simple chatting application using the JSQMessagesViewController. I read & followed JSQMessagesViewController sample project (https://www.syncano.io/blog/create-ios-chat-app-part1/) but my code occurs an error.
class ChatRoomViewController: JSQMessagesViewController {
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]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.setup()
self.addDemoMessages()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func reloadMessagesView() {
self.collectionView?.reloadData()
}
func gotoReserve() {
let nextViewController = ReserveViewController(nibName: "ReserveViewController", bundle: nil)
self.navigationController!.pushViewController(nextViewController, animated: true)
}
}
//MARK - Setup
extension ChatRoomViewController {
func addDemoMessages() {
let message = JSQMessage(senderId: "zzz", displayName: "aaa", text: "asasd")
self.messages.append(message)
self.reloadMessagesView()
}
func setup() {
self.senderId = "user"
self.senderDisplayName = "user"
}
}
//MARK - Data Source
extension ChatRoomViewController {
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 ChatRoomViewController {
override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
let message = JSQMessage(senderId: senderId, senderDisplayName: senderDisplayName, date: date, text: text)
self.messages += [message]
self.finishSendingMessage()
}
override func didPressAccessoryButton(sender: UIButton!) {
}
}
When I run it,
the error message say
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[JSQMessage messageHash]: unrecognized selector sent to instance 0x7fdf0a6e1f60'
and the app is terminated.
But it runs well after erasing
self.addDemoMessages()
code in viewDidLoad(). So I guess this code occurs error, but I don't know how to fix :( How can I fix it?

I had a similar problem with this when I subclassed the JSQMessage object. If you are doing that you just need to make sure that you implement the has for the message data. I just had to add this method to my message object
func messageHash() -> UInt {
return UInt(self.hash)
}
Hope that helps 👻

Here is my case,
to solve it by
Add -all_load to the other linker flags in your build settings.
-all_load forces the linker to load all object files from every archive it sees, even those without Objective-C code.
and
-force_load is available in Xcode 3.2 and later. It allows finer grain control of archive loading.
Each -force_load option must be followed by a path to an archive, and every object file in that archive will be loaded.

Related

InputToolbar Send button not working

I am working with JSQMessagesViewController and Firebase to implement a chat feature in my app. I had everything working well in my ChatViewController. But now I have moved my ChatViewController into a container view that is within a parent view controller and now the "send" button does not work when the keyboard is expanded.
In other words, in order to send a chat message, I must call view.endEditing(true) on the parent view controller that itself is within a UITabBarController, and then the send button will work. But as long as the keyboard is expanded, the send button doesn't respond. below is my ChatViewController code...
import Foundation
import UIKit
import FirebaseDatabase
import JSQMessagesViewController
final class ChatViewController: JSQMessagesViewController {
var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!
var fireRootRef: FIRDatabaseReference!
var chatMessages = [JSQMessage]()
var messagesRefHandle: UInt!
var chatChannelId: String!
override func viewDidLoad(){
super.viewDidLoad()
self.inputToolbar.contentView.textView.backgroundColor = UIColor.clear
inputToolbar.alpha = 0.7
...
}
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool){
super.viewDidAppear(animated)
}
func setupView(){
...
}
override func viewWillDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
removeChatObserver()
}
func removeChatObserver(){
...
}
private func setupMessageBubbles() {
...
}
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return chatMessages.count
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = chatMessages[indexPath.item]
if message.senderId == senderId {
return outgoingBubbleImageView
} else {
return incomingBubbleImageView
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = chatMessages[indexPath.item]
if message.senderId == senderId {
cell.textView!.textColor = UIColor.white
} else {
cell.textView!.textColor = UIColor.black
}
return cell
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAt indexPath: IndexPath!) -> CGFloat {
let message = chatMessages[indexPath.item]
if message.senderId == self.senderId {
return 0
}
if indexPath.item > 0 {
let previousMessage = chatMessages[indexPath.item - 1]
if previousMessage.senderId == message.senderId {
return 0
}
}
return kJSQMessagesCollectionViewCellLabelHeightDefault
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath!) -> NSAttributedString! {
let message = chatMessages[indexPath.item]
switch message.senderId {
case senderId:
return nil
default:
guard let senderDisplayName = message.senderDisplayName else {
assertionFailure()
return nil
}
return NSAttributedString(string: senderDisplayName)
}
}
//no avatar images
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
print("DID PRESS SEND")
let fireMessagesRef = fireRootRef.child("messages").child(chatChannelId)
let itemRef = fireMessagesRef.childByAutoId()
let messageItem = [
"text": text,
K.MessageKeys.senderIdKey: senderId,
"displayName": senderDisplayName,
]
itemRef.setValue(messageItem)
JSQSystemSoundPlayer.jsq_playMessageSentSound()
finishSendingMessage()
}
override func didPressAccessoryButton(_ sender: UIButton!) {
//
}
private func observeMessages() {
...
}
func addMessage(id: String, text: String, name: String) {
...
}
}
I would like to fix the send button so the user can tap send when the keyboard is expanded. It is interesting that in order to dismiss the keyboard I have to call view.endEditing(true) on the parent view controller and not on the child view itself. This made me think that I need to configure the button action on the parent view however i haven't had any success. Thanks for your help
What I guess is jsq collection view cover the input view, so you are pressing on collection view, not the send button. Put a breakpoint on - (void)jsq_didReceiveKeyboardWillChangeFrameNotification:(NSNotification *)notification in JSQMessagesViewController, check whether CGRectGetHeight(keyboardEndFrame) and insets.bottom for setting collection view bottom is sufficient space to show the input view in your container. A problem is that the jsq controller use autolayout to adjust subviews, the collection view is align with its topLayoutGuide and bottomLayoutGuide which is the view controller thing, when you put a view controller inside another view controller, that may cause confusion.
Take iPhone 6(s)/7 plus for example, the keyboard height is 271, the inputtoolbar is 44 height in controller, so the total height is 315. Therefore CGRectGetHeight(keyboardEndFrame) + insets.bottom should be 315. Put a breakpoint on that line, check whether the sum is 315, if not, that means something calculated wrong.
Update for solution
If the cause is indeed mentioned above, try this to solve the problem
self.additionalContentInset = UIEdgeInsets(top: 0, left: 0, bottom: 44, right: 0)
This will add a bottom inset to the collection view. Add this after viewDidLoad

When Using JSQMessagesViewController the UI does not appear

I am trying to implement JSQMessagesViewController, and I am following the Ray Wenderlich tutorial(https://www.raywenderlich.com/122148/firebase-tutorial-real-time-chat), but the UI is not appearing on my view controller, it is just blank.
This is my pod file.
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
target "Sell Goods" do
pod 'Firebase'
pod 'Firebase/Storage'
pod 'Firebase/Database'
pod 'Google/SignIn'
pod 'JSQMessagesViewController'
end
I have tried using a bridging header but that did not work, and I followed the tutorial step by step.
Segue to ChatViewController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let editingVC = segue.destinationViewController as? ChatsViewController
else {
preconditionFailure("Bad")
}
editingVC.senderId = userID
editingVC.senderDisplayName = userName
}
ChatViewController
import Firebase
import JSQMessagesViewController
class ChatsViewController: JSQMessagesViewController {
var messages = [JSQMessage]()
var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!
override func viewDidLoad() {
title = "ChatChat"
setupBubbles()
//collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
//collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
resetScreen = true
}
private func setupBubbles() {
let factory = JSQMessagesBubbleImageFactory()
outgoingBubbleImageView = factory.outgoingMessagesBubbleImageWithColor(
UIColor.jsq_messageBubbleBlueColor())
incomingBubbleImageView = factory.incomingMessagesBubbleImageWithColor(
UIColor.jsq_messageBubbleLightGrayColor())
}
override func collectionView(collectionView: JSQMessagesCollectionView!,
messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item]
if message.senderId == senderId {
return outgoingBubbleImageView
} else {
return incomingBubbleImageView
}
}
override func collectionView(collectionView: JSQMessagesCollectionView!,
messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(collectionView: JSQMessagesCollectionView!,
avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
}
You must call super.viewDidLoad & super.viewDidDisappear

UICollectionView is never called with JSQMessagesCollectionView

I am using the https://www.raywenderlich.com/122148/firebase-tutorial-real-time-chat and I have reached "Creating Messages" portion and my collection view seems to never have been called. I had to take out all the override statement before the collection view functions because I was getting errors with them. I have tried reloading the collection view with
self.collectionView!.reload()
Any ideas?
Here is my code:
Created by Ari on 7/19/16.
// Copyright © 2016 Logical Nonsense LLC. All rights reserved.
//
import UIKit
import JSQMessagesViewController
class ChatViewController: JSQMessagesViewController {
var messages = [JSQMessage]()
var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.reloadData()
print("hi")
title = "ChatChat"
setupBubbles()
// Do any additional setup after loading the view.
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSize(width: 0, height: 0)
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSize(width: 0, height: 0)
addMessage(id: "foo", text: "Hey person!")
// messages sent from local sender
addMessage(id: senderId, text: "Yo!")
addMessage(id: senderId, text: "I like turtles!")
print("hi")
// animates the receiving of a new message on the view
finishReceivingMessage()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.collectionView!.reloadData()
// messages from someone else
addMessage(id: "foo", text: "Hey person!")
// messages sent from local sender
addMessage(id: senderId, text: "Yo!")
addMessage(id: senderId, text: "I like turtles!")
print("hi")
// animates the receiving of a new message on the view
finishReceivingMessage()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(collectionView: JSQMessagesCollectionView!,
messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidAppear(true)
}
func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.senderId == senderId { // 2
return outgoingBubbleImageView
} else { // 3
return incomingBubbleImageView
}
}
func collectionView(collectionView: JSQMessagesCollectionView!,
avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
func addMessage(id: String, text: String) {
print(text)
let message = JSQMessage(senderId: id, displayName: "", text: text)
messages.append(message!)
self.collectionView!.reloadData()
}
private func setupBubbles() {
let factory = JSQMessagesBubbleImageFactory()
outgoingBubbleImageView = factory?.outgoingMessagesBubbleImage(
with: UIColor.jsq_messageBubbleBlue())
incomingBubbleImageView = factory?.incomingMessagesBubbleImage(
with: UIColor.jsq_messageBubbleLightGray())
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}

Issue with JSQMessages Displaying Test Messages

When I add multiple test messages, only two of them are displaying on screen. The other messages are clearly there because I can copy them, but the colors are simply not showing. When I scroll around, a new two texts appear, but it is still only two. Below show some examples.
First screenshot shows when the screen first loads. second shows when I move it around. Third shows that the other messages do exist but are not visible. Any ideas on how to fix this? Also, how do I make the names appear? Is there a good guide to doing this in Swift?
Here is the code I used:
var messages = [JSQMessage]()
var incomingBubbleImageView = JSQMessagesBubbleImageFactory.incomingMessageBubbleImageViewWithColor(UIColor.jsq_messageBubbleLightGrayColor())
var outgoingBubbleImageView = JSQMessagesBubbleImageFactory.outgoingMessageBubbleImageViewWithColor(UIColor.jsq_messageBubbleGreenColor())
override func viewDidLoad() {
super.viewDidLoad()
self.sender = UIDevice.currentDevice().identifierForVendor?.UUIDString
messages += [JSQMessage(text: "hello", sender: self.sender)]
messages += [JSQMessage(text: "hello", sender: "other")]
messages += [JSQMessage(text: "hello", sender: self.sender)]
messages += [JSQMessage(text: "hello", sender: "other")]
messages += [JSQMessage(text: "hello", sender: self.sender)]
messages += [JSQMessage(text: "hello", sender: "other")]
reloadMessagesView()
}
func reloadMessagesView() {
self.collectionView?.reloadData()
}
And here is the extension code for the delegate methods:
extension TestJSQ {
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(self.messages.count)
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, bubbleImageViewForItemAtIndexPath indexPath: NSIndexPath) -> UIImageView {
let data = messages[indexPath.row]
switch(data.sender) {
case self.sender:
return self.outgoingBubbleImageView
default:
return self.incomingBubbleImageView
}
}
override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageViewForItemAtIndexPath indexPath: NSIndexPath!) -> UIImageView! {
return nil
}
}
Any help would be much appreciated!!!
You need to call self.finishReceivingMessage() after you add the message(s) to the data source (which will then invalidate layout BEFORE call collectionView.reloadData() for you)

JSQMessagesViewController message bubbles are aligning irregular

I am developing an app using JSQMessagesViewController. But there is problem that message bubbles are aligning irregular order in collectionView.
Here is a Screenshot
Here is the code:
import UIKit
class mDetailContainerViewController: JSQMessagesViewController, JSQMessagesCollectionViewDelegateFlowLayout{
var userName = ""
var messages = [JSQMessage]()
let incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor.lightGrayColor())
let outgoingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor.greenColor())
override func viewDidLoad() {
super.viewDidLoad()
self.userName = "iPhone"
for i in 1...10{
var sender = (i%2 == 0) ? "Syncano" : self.userName
var message = JSQMessage(senderId: sender, displayName: sender, text: "Text")
self.messages += [message]
}
self.collectionView.reloadData()
self.senderDisplayName = self.userName
self.senderId = self.userName
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
var data = self.messages[indexPath.row]
return data
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
var data = self.messages[indexPath.row]
if (data.senderId == self.senderId){
return self.outgoingBubble
}else{
return self.incomingBubble
}
}
override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.messages.count
}
override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
var message = JSQMessage(senderId: senderId, displayName: senderDisplayName, text: text)
messages += [message]
self.finishSendingMessage()
}
override func didPressAccessoryButton(sender: UIButton!) {
}
I searched the solution a bit on the internet, but all the instructions are just complicated. I did not figure out anything. Besides, I am not sure that I found a solution. If someone will explain the solution simply, I will be pleased.
Note: JSQMessagesViewController is shown in container view.
Lastly, how can I change the send button title and text field placeholder with using localizations.
Thanks.
I am not familiar with this SDK but I think it is because both of these lines
let incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor.lightGrayColor())
let outgoingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor.greenColor())
are incomingMessagesBubbleImageWithColor. Set outgoingBubble to JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImageWithColor (if that exists)
And I've never used this message class before but I believe your alignment issue with the green text might be solved with something like...
outgoingBubble.textLabel.textAlignment = NSTextAlignment.Center
To fix the bubble locations, remove the avatar images with this code;
self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero
For anybody having troubles with this in the future check that your code doesnt look like this:
override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let data = messages[indexPath.row]
if data.senderId == currentUser.objectId! {
return incomingBubble
} else {
return outgoingBubble
}
}
it should look like this:
override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let data = messages[indexPath.row]
if data.senderId == PFUser.currentUser()!.objectId! {
return outgoingBubble
} else {
return incomingBubble
}
}
that fixed it for me!
in Swift 3:
For your specific problem that's centering the text inside the bubble:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
cell.textView.textAlignment = .center
return cell
}

Resources