I building an app that allows users to communicate with an AWS Lex chatbot, however the problem is right now is when the user taps the button to begin their conversation with the bot it crashes and gives me a Thread 1:EXC_BAD_INSTRUCTION error on this line:
let configuration = AWSServiceConfiguration(region: botRegion!, credentialsProvider: AWSIdentityManager.default().credentialsProvider)
I would really like to know what is causing it to behave like this, as Xcode does not highlight any significant parts of the code.
Below is the what my code looks like in its entirety for those interested.
import Foundation
import UIKit
import Photos
import JSQMessagesViewController
import AWSLex
import AWSMobileHubHelper
let ClientSenderId = "Client"
let ServerSenderId = "Server"
class JeevesChatViewController: JSQMessagesViewController, JSQMessagesComposerTextViewPasteDelegate {
// The name of the bot.
var botName: String?
// The bot region
var botRegion: AWSRegionType?
// The bot alias
var botAlias: String?
// The messages communicated between the client and the server
var messages: [JSQMessage]?
// The interaction kit client
var interactionKit: AWSLexInteractionKit?
// The session attributes
var sessionAttributes: [AnyHashable: Any]?
// Contents of outgoing image
var outgoingBubbleImageData: JSQMessagesBubbleImage?
// Contents of incoming image
var incomingBubbleImageData: JSQMessagesBubbleImage?
// Used to store task completion source of iteraction kit
var textModeSwitchingCompletion: AWSTaskCompletionSource<NSString>?
// The client image
var clientImage: JSQMessagesAvatarImage?
// The bot image
var serverImage: JSQMessagesAvatarImage?
override func viewDidLoad() {
super.viewDidLoad()
// Make the initial setup for the bot.
// Error occurs here - fatal error: unexpectedly found nil while unwrapping an Optional value
let configuration = AWSServiceConfiguration(region: botRegion!, credentialsProvider: AWSIdentityManager.default().credentialsProvider)
// Setup interaction kit configuration
let botConfig = AWSLexInteractionKitConfig.defaultInteractionKitConfig(withBotName:botName!, botAlias: botAlias!)
// Disable automatic voice playback
botConfig.autoPlayback = false
// Register the interaction kit client
AWSLexInteractionKit.register(with: configuration!, interactionKitConfiguration: botConfig, forKey: botName!)
// Fetch and set the interaction kit client
self.interactionKit = AWSLexInteractionKit.init(forKey: botName!)
// Set the interaction kit delegate
// Remove 'as? AWSLexInteractionDelegate' statement later
self.interactionKit?.interactionDelegate = self
// Setup JSQMessagesViewController configuration
self.showLoadEarlierMessagesHeader = false
// Initialise the avatars for client and server here.
// Setup the default keyboard type.
self.inputToolbar.contentView?.textView?.keyboardType = UIKeyboardType.default
// Initialise the messages list
self.messages = [JSQMessage]()
// Set the colours for messages bubbles
let bubbleFactory = JSQMessagesBubbleImageFactory()
self.outgoingBubbleImageData = bubbleFactory?.outgoingMessagesBubbleImage(with: UIColor.jsq_messageBubbleGreen())
self.incomingBubbleImageData = bubbleFactory?.incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue())
self.inputToolbar.contentView?.leftBarButtonItem = nil
self.senderDisplayName = "User"
self.senderId = ClientSenderId
}
// MARK: - JSQMessagesViewController delegate methods
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!)
if let textModeSwitchingCompletion = textModeSwitchingCompletion {
textModeSwitchingCompletion.set(result: text as NSString)
self.textModeSwitchingCompletion = nil
}
else {
self.interactionKit?.text(inTextOut: text)
}
self.finishSendingMessage(animated: true)
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, messageDataForItemAt indexPath: IndexPath) -> JSQMessageData {
return self.messages![indexPath.item]
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, didDeleteMessageAt indexPath: IndexPath) {
// Nothing happens here
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, messageBubbleImageDataForItemAt indexPath: IndexPath) -> JSQMessageBubbleImageDataSource {
let message = self.messages![indexPath.item]
if (message.senderId == self.senderId) {
return self.outgoingBubbleImageData!
}
return self.incomingBubbleImageData!
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, avatarImageDataForItemAt indexPath: IndexPath) -> JSQMessageAvatarImageDataSource? {
let message = messages![indexPath.item]
if message.senderId == ClientSenderId {
return self.clientImage
}
return self.serverImage
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let messages = messages {
return messages.count
}
return 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = (super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell)
let msg = self.messages?[indexPath.item]
if !msg!.isMediaMessage {
if (msg?.senderId == self.senderId) {
cell.textView?.textColor = UIColor.black
}
else {
cell.textView?.textColor = UIColor.white
}
}
return cell
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, attributedTextForCellTopLabelAt indexPath: IndexPath) -> NSAttributedString? {
if indexPath.item % 3 == 0 {
let message = self.messages?[indexPath.item]
return JSQMessagesTimestampFormatter.shared().attributedTimestamp(for: message!.date)
}
return nil
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath) -> NSAttributedString? {
let message = self.messages?[indexPath.item]
// iOS 7 sender name labels
if (message?.senderId == self.senderId) {
return nil
}
if indexPath.item - 1 > 0 {
let previousMessage = self.messages?[indexPath.item - 1]
if (previousMessage?.senderId == message?.senderId) {
return nil
}
}
// Do not specify attributes to use the default values.
return NSAttributedString(string: message!.senderDisplayName)
}
override func collectionView(_ collectionView: JSQMessagesCollectionView, attributedTextForCellBottomLabelAt indexPath: IndexPath) -> NSAttributedString? {
return nil
}
func composerTextView(_ textView: JSQMessagesComposerTextView, shouldPasteWithSender sender: Any) -> Bool {
return true
}
}
// MARK: - Bot Interaction Kit
extension JeevesChatViewController: AWSLexInteractionDelegate {
func interactionKit(_ interactionKit: AWSLexInteractionKit, onError error: Error) {
print("Error occurred: \(error)")
}
func interactionKit(_ interactionKit: AWSLexInteractionKit, switchModeInput: AWSLexSwitchModeInput, completionSource: AWSTaskCompletionSource<AWSLexSwitchModeResponse>?) {
self.sessionAttributes = switchModeInput.sessionAttributes
DispatchQueue.main.async(execute: {
let message: JSQMessage
// Handle a successful transaction
if (switchModeInput.dialogState == AWSLexDialogState.readyForFulfillment) {
// Currently just displaying the slots return on ready for fulfillment
if let slots = switchModeInput.slots {
message = JSQMessage(senderId: ServerSenderId, senderDisplayName: "", date: Date(), text: "Slots:\n\(slots)")
self.messages?.append(message)
self.finishSendingMessage(animated: true)
}
} else {
message = JSQMessage(senderId: ServerSenderId, senderDisplayName: "", date: Date(), text: switchModeInput.outputText!)
self.messages?.append(message)
self.finishSendingMessage(animated: true)
}
})
// This can be expanded to take input from the user.
let switchModeResponse = AWSLexSwitchModeResponse()
switchModeResponse.interactionMode = AWSLexInteractionMode.text
switchModeResponse.sessionAttributes = switchModeInput.sessionAttributes
completionSource?.set(result: switchModeResponse)
}
func interactionKitContinue(withText interactionKit: AWSLexInteractionKit, completionSource: AWSTaskCompletionSource<NSString>) {
textModeSwitchingCompletion = completionSource
}
}
You haven't set your credentialsProvider properly.
AWSIdentityManager was never initialized to be called:
let configuration = AWSServiceConfiguration(region: botRegion!, credentialsProvider: AWSIdentityManager.default().credentialsProvider)
if you are using AWS for S3 bucket you should use incognito and thus
let cognitoIdentityPoolId = //"XYZ"
let cognitoUnauthRoleArn = //"XYZ"
let cognitoAuthRoleArn = //"XYZ"
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: #Wherever, identityPoolId: cognitoIdentityPoolId, unauthRoleArn: cognitoUnauthRoleArn, authRoleArn: cognitoAuthRoleArn, identityProviderManager: nil)
which you can then change your line to:
let configuration = AWSServiceConfiguration(region: botRegion!, credentialsProvider: credentialsProvider)
Related
I have a list of machine name and type in tableView and when user click it, it push into detailVC.
At first the the user don't have an image, than user choose an image from gallery and return it as a PHAssets.
Than I convert the PHAssets into data and show it in collectionView, so I create an array of data and display it in collectionView.
Than I want to update MachineItem object to my data from PHAssets, since in MachineItem object have an array of Data.
So when user return from machine list in tableView, the object already update. But when I tap the list from tableView. The photo I save in an object is not showing, how can display it in my collectionView.
This is my Model
struct MachineItem: Codable {
var id = UUID().uuidString
var name: String
var type: String
var qrNumber = Int.random(in: 1..<10)
var maintenanceDate: String?
var images: [Data]?
}
This is my MachineStore class that perform all add, update, and remove object from MachineItem
class MachineStore {
var items: [MachineItem] = []
#discardableResult func add(_ machine: MachineItem, at index: Int) -> MachineItem {
let newMachine = MachineItem(id: machine.id, name: machine.name, type: machine.type, qrNumber: machine.qrNumber, maintenanceDate: machine.maintenanceDate, images: machine.images)
items.insert(newMachine, at: index)
return newMachine
}
func update(_ machine: MachineItem) {
if let index = items.firstIndex(where: { $0.id == machine.id }) {
items[index].name = machine.name
items[index].type = machine.type
items[index].qrNumber = machine.qrNumber
items[index].maintenanceDate = machine.maintenanceDate
items[index].images = machine.images
}
}
#discardableResult func remove(at index: Int) -> MachineItem {
return items.remove(at: index)
}
}
This is my MachineDataVC that have a list of MachineItem Object in TableView
class MachineDataVC: UIViewController {
var tableView = UITableView()
var store = MachineStore()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = store.items[indexPath.row]
let detailVC = MachineDetailVC()
detailVC.item = item
detailVC.store = store
navigationController?.pushViewController(detailVC, animated: true)
tableView.deselectRow(at: indexPath, animated: true)
}
}
And this is the detailVC where I can't display the images data when back and fort from MachineDataVC to detailVC
class MachineDetailVC: UIViewController {
var item: MachineItem!
var store: MachineStore!
var images: [Data] = []
var photoCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
override func viewDidLoad() {
super.viewDidLoad()
// CollectionView setup
photoCollectionView.delegate = self
photoCollectionView.dataSource = self
photoCollectionView.register(MachineDetailCell.self, forCellWithReuseIdentifier: MachineDetailCell.cellID)
photoCollectionView.backgroundColor = .systemBackground
}
// This where PHAssets being retrieve
#objc func pickPhotos() {
let imagePickerVC = ImagePickerController()
imagePickerVC.settings.selection.max = 10
imagePickerVC.settings.theme.selectionStyle = .numbered
imagePickerVC.settings.fetch.assets.supportedMediaTypes = [.image]
imagePickerVC.settings.selection.unselectOnReachingMax = true
self.presentImagePicker(imagePickerVC) { (assets) in
} deselect: { (_) in
} cancel: { (_) in
} finish: { (assets) in
self.images = self.getImage(from: assets)
self.photoCollectionView.reloadData()
}
}
private func getImage(from assets: [PHAsset]) -> [Data] {
let images = assets.map { fetchImage(from: $0) }
print("Pick image:", images)
return images
}
private func fetchImage(from asset: PHAsset) -> Data {
let manager = PHImageManager.default()
let options = PHImageRequestOptions()
options.isSynchronous = true
var thumbnail = Data()
manager.requestImage(for: asset, targetSize: .init(width: 100, height: 100), contentMode: .aspectFill, options: options) { (result, info) in
if let selectedImage = result?.data {
thumbnail = selectedImage
}
}
return thumbnail
}
// This is how I setup collectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MachineDetailCell.cellID, for: indexPath) as! MachineDetailCell
let image = images[indexPath.row]
cell.set(imageData: image)
return cell
}
// This is how I save the object from UIBarButtonItem
#objc func saveItem() {
guard let name = machineNameTF.text else { return }
guard let type = machineTypeTF.text else { return }
guard let date = machineMaintenanceDateTF.text else { return }
let machineItem = MachineItem(id: item.id, name: name, type: type, qrNumber: item.qrNumber, maintenanceDate: date, images: images)
store.update(machineItem)
}
}
What I've been trying so far is like this
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if !images.isEmpty {
return store.items.count
} else {
return images.count
}
}
I'm very glad if anyone can help me :)
I finally found my answer from my question, what I need to do is in my viewDidLoad I need to add in my MachineDetailVC
self.images = self.item.images ?? []
since when I save the data back into items in machineStore, I need to give the data into images array in MachineDetailVC
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.
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.
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.
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