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.
Related
I have tableview listed pdfs by retrieving from Firebase. No problem with retrieving datas and sending default SMTP. What I am trying to do that selected urls(pdf) in tableview, will be sent by using SMTP to user email so that user can easily reach pdf urls. I could not figure out how to handle it. This is the only critical point in my project. Any help is appreciated.
import UIKit
import Firebase
import PDFKit
import skpsmtpmessage
class IcsViewcontroller: UIViewController , UISearchBarDelegate,SKPSMTPMessageDelegate{
var preImage : UIImage?
let cellSpacingHeight: CGFloat = 20
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var pdfListView: UITableView!
#IBOutlet weak var spinner: UIActivityIndicatorView!
var pdfList = [pdfClass]()
var searchall = [pdfClass]()
var searching = false
var selectedPdf = [pdfClass]()
override func viewDidLoad() {
super.viewDidLoad()
let editButton = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(showEditing(_:)))
navigationItem.rightBarButtonItem = editButton
pdfListView.delegate = self
pdfListView.dataSource = self
searchBar.delegate = self
self.pdfListView.isHidden = true
getPdf()
sendEmail()
searchBar.searchTextField.backgroundColor = .white
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let selectionIndexPath = self.pdfListView.indexPathForSelectedRow {
self.pdfListView.deselectRow(at: selectionIndexPath, animated: animated)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if searching {
let destination = segue.destination as! PdfKitViewController
let selectedIndexPath = pdfListView.indexPathForSelectedRow
destination.pdf = searchall[selectedIndexPath!.row]
} else {
let destination = segue.destination as! PdfKitViewController
let selectedIndexPath = pdfListView.indexPathForSelectedRow
destination.pdf = pdfList [selectedIndexPath!.row]
}
}
#objc func showEditing(_ sender: UIBarButtonItem)
{
if(self.pdfListView.isEditing == true)
{
self.pdfListView.isEditing = false
self.navigationItem.rightBarButtonItem?.title = "Edit"
}
else
{
self.pdfListView.allowsMultipleSelectionDuringEditing = true
self.pdfListView.isEditing = true
self.navigationItem.rightBarButtonItem?.title = "Done"
}
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
if searchBar.text == nil || searchBar.text == "" {
searching = false
} else {
searching = true
}
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
self.pdfListView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searching = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
searchall = pdfList
searching = false
pdfListView.reloadData()
} else {
searching = true
searchall = pdfList.filter({($0.pdf_name?.lowercased().prefix(searchText.count))! == searchText.lowercased() })
pdfListView.reloadData()
}
}
func getPdf () {
spinner.startAnimating()
let docRef = Storage.storage().reference().child("ICS_Documents")
docRef.listAll{ (result , error ) in
if error != nil {
let alert = UIAlertController(title: "Error", message: "No Database Connection", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
self.spinner.stopAnimating()
}
for item in result.items {
let storeageLocation = String( describing : item)
let gsReference = Storage.storage().reference(forURL: storeageLocation)
gsReference.downloadURL{ url , error in
if let error = error{
print(error)
} else {
let pdf_name = String( item.name)
let pdf_url = url?.absoluteString
let thumbnailSize = CGSize(width: 100, height: 100)
let thmbnail = self.generatePdfThumbnail(of: thumbnailSize, for: url!, atPage: 0)
let pdfall = pdfClass(pdf_name: pdf_name, pdf_url: pdf_url!, pdf_preview: thmbnail!)
self.pdfList.append(pdfall)
}
DispatchQueue.main.async {
self.pdfList.sort{ $0.pdf_name ?? "" < $1.pdf_name ?? ""}
self.pdfListView.reloadData()
self.spinner.stopAnimating()
self.pdfListView.isHidden = false
}
}
}
}
}
func generatePdfThumbnail(of thumbnailSize: CGSize , for documentUrl: URL, atPage
pageIndex: Int) -> UIImage? {
let pdfDocument = PDFDocument(url: documentUrl)
let pdfDocumentPage = pdfDocument?.page(at: pageIndex)
return pdfDocumentPage?.thumbnail(of: thumbnailSize, for:
PDFDisplayBox.trimBox)
}
func sendEmail() {
let message = SKPSMTPMessage()
message.relayHost = "smtp.gmail.com"
message.login = "xxx#gmail.com"
message.pass = "******"
message.requiresAuth = true
message.wantsSecure = true
message.relayPorts = [587]
message.fromEmail = "xxxx#gmail.com"
message.toEmail = "xxxx#gmail.com"
message.subject = "subject"
let messagePart = [kSKPSMTPPartContentTypeKey: "text/plain; charset=UTF-8",
kSKPSMTPPartMessageKey: "Hi alll"]
message.parts = [messagePart]
message.delegate = self
message.send()
}
func messageSent(_ message: SKPSMTPMessage!) {
print("Successfully sent email!")
}
func messageFailed(_ message: SKPSMTPMessage!, error: Error!) {
print("Sending email failed!")
}
}
extension IcsViewcontroller : UITableViewDelegate,UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if searching{
return searchall.count
}else {
return pdfList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "pdfCell", for: indexPath) as! pdfCellTableViewCell
let varcell : pdfClass
if searching {
varcell = searchall [indexPath.row]
} else {
varcell = pdfList [indexPath.row]
}
cell.configure(name: varcell.pdf_name! , pdfthumbnail: varcell.pdf_preview!)
cell.accessoryType = .disclosureIndicator
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var indx : pdfClass
if searching{
indx = searchall[indexPath.row ]
}else {
indx = pdfList[indexPath.row]
}
self.selectdeselectcell(tableview: tableView, indexpath: indexPath)
print("selected")
if pdfListView.isEditing {
func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return !tableView.isEditing
}
}else{
performSegue(withIdentifier: "toPdfKit", sender: indx)
print(indexPath.row)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.selectdeselectcell(tableview: tableView, indexpath: indexPath)
print("deselected")
}
func selectdeselectcell(tableview : UITableView ,indexpath : IndexPath){
if pdfListView.isEditing{
self.selectedPdf.removeAll()
if let arr = pdfListView.indexPathForSelectedRow{
print(arr)
}
}else {
return
}
}
/* func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action:UITableViewRowAction,indexPath:IndexPath)-> Void in
let storage = Storage.storage()
var childsURL : String
if self.searching {
childsURL = self.searchall[indexPath.row ].pdf_url!
}else{
childsURL = self.pdfList[indexPath.row].pdf_url!
}
let storageref = storage.reference(forURL: childsURL)
storageref.delete{ error in
if let error = error {
print(error.localizedDescription)
} else{
print("File deleted")
}
}
self.pdfList.removeAll()
self.getPdf()
})
return [deleteAction]
}*/
}
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.
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
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
Hi I am trying to figure out how to make it so that all my data for my timeline can reload on its own. Here is my current code for the whole TimeLineViewController
import UIKit
import CoreData
class TimelineTableViewController: UITableViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate, UIAlertViewDelegate {
var timelineData:NSMutableArray! = NSMutableArray()
override init(style: UITableViewStyle) {
super.init(style: style)
// Custom initialization
}
#IBAction func reportButton(sender: AnyObject) {
performSegueWithIdentifier("reportSegue", sender: self)
}
#IBAction func composeStatus(sender: AnyObject) {
performSegueWithIdentifier("composeSegue", sender:self)
}
#IBAction func logout(sender: AnyObject) {
PFUser.logOut()
var currentUser = PFUser.currentUser() // this will now be nil
performSegueWithIdentifier("logoutSegue", sender:self)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func loadData(){
timelineData.removeAllObjects()
var findTimelineData:PFQuery = PFQuery(className: "woofs")
findTimelineData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in
if error == nil{
for object in objects{
let woofs:PFObject = object as PFObject
self.timelineData.addObject(woofs)
}
let array:NSArray = self.timelineData.reverseObjectEnumerator().allObjects
self.timelineData = NSMutableArray(array: array)
self.tableView.reloadData()
}
}
}
override func viewDidAppear(animated: Bool) {
self.loadData()
}
func refresh(sender:AnyObject)
{
// Updating your data here...
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
override func viewDidLoad() {
super.viewDidLoad()
var statusBarHidden: Bool
/*var newFeatureAlert:UIAlertController = UIAlertController(title: "Attention", message: "New feature added. You can now Report content that you find offensive. Just tap on Report to get the instructions on how to.", preferredStyle: UIAlertControllerStyle.Alert)
newFeatureAlert.addAction(UIAlertAction(title: "AWESOME!", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(newFeatureAlert, animated: true, completion: nil)*/
self.refreshControl?.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
UITextView.appearance().tintColor = UIColor.blackColor().colorWithAlphaComponent(1.0)
UITextView.appearance().backgroundColor = UIColor.clearColor().colorWithAlphaComponent(0.4)
self.tableView.backgroundView = UIImageView(image: UIImage(named: "hdwallpaper"))
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return timelineData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:PAHTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as PAHTableViewCell
cell.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.3)
let woofers:PFObject = self.timelineData.objectAtIndex(indexPath.row) as PFObject
cell.pahTextView.alpha = 0
cell.timestampLabel.alpha = 0
cell.usernameLabel.alpha = 0
cell.pahTextView.text = woofers.objectForKey("content") as String
var dataFormatter:NSDateFormatter = NSDateFormatter()
dataFormatter.dateFormat = "dd-MM-yyyy HH:mm"
cell.timestampLabel.text = dataFormatter.stringFromDate(woofers.createdAt)
var findwoofers:PFQuery = PFUser.query()
findwoofers.whereKey("objectId", equalTo: woofers.objectForKey("woofers").objectId)
findwoofers.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in if error == nil{
if let user:PFUser = (objects as NSArray).lastObject as? PFUser{
cell.usernameLabel.text = user.username
cell.timestampLabel.alpha = 0
cell.profileImageView.alpha = 0
cell.pahTextView.alpha = 0
if let profileImage = user["profileImage"] as? PFFile{
profileImage.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
let image = UIImage(data:imageData)
cell.profileImageView.image = image
}else{
let image = UIImage(named: "BLUEPAW")
}
}
}
}
}
UIView.animateWithDuration(1.0, animations:{
cell.profileImageView.alpha = 1
cell.usernameLabel.alpha = 2
cell.timestampLabel.alpha = 4
cell.pahTextView.alpha = 3
})
}
return cell
}
let me know what I need to change to make this work. Thank You.