4

I came across what looks to be a very complex and customize library that would be pretty useful for any app that wants to have a messaging system built in. JSQMessagesViewController on Github. While I was trying to implement the library on my own I ran into some problems. One I don't quite understand the terminology of "protocols" and "classes" (I understand a little about classes). The first problem was the I couldn't use PFObjects in the CollectionView because an instance of JSQMessage needs to be passed through most of the custom methods. Now, I know how to take in a PFObject an get the properties from that like

self.eachMessage = [self.messages objectAtIndex:indexPath.row]; //each message
self.eachMessage[@"message"] //message from the PFObject
self.eachMessage[@"sender"] //sender from the PFObject

The JSQMessage class has custom properties that would represent the properties of the PFObject like

JSQMessage *message = [[JSQMessage alloc] init]; //initialize it
message.senderId //could be the objectId of the user
message.senderDisplayName //the user's username
message.text //text of the message
message.date //time sent of the message

The thing is, in the custom class JSQMessage...all of these properties are readonly. I am sure I can go in and change that so I can assign them to exactly what I want but there must be something I am missing here. I will attach everything in my .h and .m files. When I do send a message the only thing that comes through is the text and I believe that is because it picks up when comes from the textView on the inputToolbar.

.h File

#import <UIKit/UIKit.h>
#import <JSQMessagesViewController/JSQMessages.h>
#import <Parse/Parse.h>
#import <JSQMessagesViewController/JSQMessagesBubbleImageFactory.h>


@interface ConvoViewController : JSQMessagesViewController 

@property (strong, nonatomic) NSMutableArray *messages;
@property (strong, nonatomic) PFUser *sender;
@property (strong, nonatomic) PFUser *receiver;
@property (strong, nonatomic) JSQMessage *eachMessage;
@property (strong, nonatomic) PFObject *aMessage;
@property (strong, nonatomic) JSQMessagesBubbleImageFactory *bubbleImage;

@end

.m File

- (void)viewDidLoad {
    [super viewDidLoad];
    //Color of the keyboard (Dark to match everything else)
    self.inputToolbar.contentView.textView.keyboardAppearance = UIKeyboardAppearanceDark;

    //Color the inputview background
    self.inputToolbar.backgroundColor = [UIColor colorWithWhite:0 alpha:0.9];

    //Delete the avatars appearing next to the messages
    self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero;
    self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero;

    //Set the senderID
    self.senderId = self.sender.objectId;


}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:YES];

    //Query for part of the messages
    PFQuery *messages1 = [PFQuery queryWithClassName:@"Messages"];
    [messages1 whereKey:@"sender" equalTo:self.sender];
    [messages1 whereKey:@"receiver" equalTo:self.receiver];

    //Query for other part of messages
    PFQuery *messages2 = [PFQuery queryWithClassName:@"Messages"];
    [messages2 whereKey:@"sender" equalTo:self.receiver];
    [messages2 whereKey:@"receiver" equalTo:self.sender];

    //Combine those queries
    PFQuery *allMessages = [PFQuery orQueryWithSubqueries:@[messages1, messages2]];
    [allMessages findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
        self.messages = [objects mutableCopy];
        [self.collectionView reloadData];
    }];

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


#pragma mark - Send Button
- (void)didPressSendButton:(UIButton *)button withMessageText:(NSString *)text senderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date {
    [JSQSystemSoundPlayer jsq_playMessageSentSound];

    JSQMessage *message = [[JSQMessage alloc] initWithSenderId:self.sender.objectId
                                             senderDisplayName:self.sender.username
                                                          date:[NSDate date]
                                                          text:text];

    [self.messages addObject:message];
    NSLog(@"%@", text);

    [self finishSendingMessageAnimated:YES];


}

#pragma mark - JSQMessages Data Source methods

- (id<JSQMessageData>)collectionView:(JSQMessagesCollectionView *)collectionView messageDataForItemAtIndexPath:(NSIndexPath *)indexPath
{
    //Return the actual message at each indexpath.row
    return [self.messages objectAtIndex:indexPath.row];
}

- (id<JSQMessageBubbleImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath
{
    /**
     *  You may return nil here if you do not want bubbles.
     *  In this case, you should set the background color of your collection view cell's textView.
     *
     *  Otherwise, return your previously created bubble image data objects.
     */

    JSQMessage *message = [self.messages objectAtIndex:indexPath.item];
    if ([message.senderId isEqualToString:self.senderId]) {
        return [self.bubbleImage incomingMessagesBubbleImageWithColor:[UIColor orangeColor]];
    }

    return [self.bubbleImage outgoingMessagesBubbleImageWithColor:[UIColor grayColor]];
}



#pragma mark - Collection View

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    //Number of messages
    return self.messages.count;
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    //Number of sections
    return 1;
}


- (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    //Creating or initial cell for the number of index paths (number of messages)
    JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath];

    //Put our messages dictionaries into PFObject so we can put them into individual cells
    self.eachMessage = [self.messages objectAtIndex:indexPath.row];


    //Put the message object into the textView's text property
    cell.textView.text = self.eachMessage.text;

    //Setting the text color of the message bubble based upon the sender
    if ([self.eachMessage.senderId isEqualToString:self.senderId]) {
        cell.textView.textColor = [UIColor blackColor];
    } else {
        cell.textView.textColor = [UIColor whiteColor];
    }
    //Set the top label to the person who sent the message
    cell.cellTopLabel.text = [NSString stringWithFormat:@"@%@", self.eachMessage.senderId];

    //Format the bottom label to a readable date
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"MM/dd/yy h:mm a"];
    cell.cellBottomLabel.text = [dateFormatter stringFromDate:self.eachMessage.date];

    //If there is a link of some sorts in the message
    cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : cell.textView.textColor,
                                          NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) };

    //What we return into the collectionview
    return cell;

}
kygcoleman
  • 734
  • 15
  • 25
  • 1
    Check this out: https://github.com/relatedcode/NotificationChat – jsetting32 Feb 13 '15 at 02:54
  • @jsetting32 that's a beautiful answer. Seeing all of this done with Parse makes much more sense and I literally just had to just change all the different query parameters to meet mine and customize it and such a way that I wanted to. Its pretty easy to manipulate code that already been framed into what I need. You should put this as the answer because using this GitHub project as a reference is so much easier for those that are using Parse as the backend – kygcoleman Feb 13 '15 at 16:54
  • Glad that the Repo I linked helped you out! I just have one suggestion. Get into Push Notifications. This will help your app become a 'real time' chat application. Since the linked project just uses a timer to run a load messages function, it's not optimal, e.g load messages every 5 seconds. Using push notifications makes it REAL TIME. So when the other user sends a message to you, the app automatically loads the message. If you every feel like implementing this feature and need assistance, please feel free to add a comment. – jsetting32 Feb 13 '15 at 19:57
  • I have already done so my friend. I implemented push on my previous version of my app before I rebuilt it. That was another mystery at first but I figured it out after some trial and error and several tutorials. The one thing I would like to learn is how to have a user tap on a notifications and then have that directly go into the app in that specific view controller and maybe even segue it's way into the conversation. I see a little thing such as that just making the user experience that much more complete. – kygcoleman Feb 13 '15 at 20:06
  • Ahh great! As for pushing to the associated controller when a user slides/taps a notification, you can check out Parse's Anypic example. When a user posts a photo, people get notifications. Then when the notification is tapped, the app opens up to the detail controller of the photo. So if you need guidance in how to do such, just check out the parse anypic app delegate implementation. It really helped me understand how to handle notifications – jsetting32 Feb 13 '15 at 21:13
  • @jsetting32 well I will definitely give that app a look. There is probably some written stuff somewhere and or youtube examples I can watch. – kygcoleman Feb 15 '15 at 04:08

1 Answers1

0

Here's part of my code. The whole files are big and my boss wont be happy if I share those. :-)

Here's how I loaded my DemoData in my DemoMessagesViewController :

self.demoData = [[DemoModelData alloc] init];

self.demoData.users = @{self.senderId:self.senderDisplayName,
                        self.targetUserId:self.targetUserName};

UIImage *tempImg = [HTTPServerKaDost fetchImageFromUrlString:userProfile.profilePicUrl]; //replace with your code to load image

JSQMessagesAvatarImage *srcImage = [JSQMessagesAvatarImageFactory
                                    avatarImageWithImage:tempImg
                                    diameter:kJSQMessagesCollectionViewAvatarSizeDefault];

tempImg = [HTTPServerKaDost fetchImageFromUrlString:self.targetUserImageFullUrl];

JSQMessagesAvatarImage *destImage = [JSQMessagesAvatarImageFactory
                                     avatarImageWithImage:tempImg
                                     diameter:kJSQMessagesCollectionViewAvatarSizeDefault];

self.demoData.avatars = @{self.senderId:srcImage,
                          self.targetUserId:destImage};

[self.demoData loadMessages];

inside loadMessages method (added by me inside DemoModelData Class), create JSQMessage objects:

JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:[msgDict valueForKey:@"sender"]
                                     senderDisplayName:[msgDict valueForKey:@"fname"]
                                                  date:dateT
                                                  text:[msgDict valueForKey:@"data"]];

[self.messages addObject:msg];

and any time you want to refresh the chat view, call this inside DemoMessagesViewController :

[self finishReceivingMessageAnimated:YES];
[self scrollToBottomAnimated:YES];
ShahiM
  • 3,179
  • 1
  • 33
  • 58
  • I think I am understanding a bit of that. So what you did is you just changed(more like got rid of) all of the static information that was in the original demo into dynamic info that you, in your case, gathered from a website? All in the DemoModelData class and then in the ViewController is everything else was pretty much the same? – kygcoleman Feb 12 '15 at 23:46
  • Yes. Play around with the demo project a bit more and you'll get the hang of it. – ShahiM Feb 13 '15 at 07:38