I'm using JSQMessagesViewController
to get chat functionality in my app that communicates with a server. However, the chat bubbles are extremely unpredictable and their size is never adjusted correctly. The text is always cutting off because the chat bubble is not increasing its width and height to fit my message. I have the following code:
#import "ChatViewController.h"
#import "JSQMessage.h"
#import "JSQMessagesTimestampFormatter.h"
#import "JSQMessagesBubbleImage.h"
#import "JSQMessagesBubbleImageFactory.h"
#import "User.h"
#import "AuthenticationHelper.h"
#import "Chat.h"
@interface ChatViewController ()
@property(strong, nonatomic)NSMutableArray* messagesArray;
@property(strong, nonatomic)User* myUser;
@property(strong, nonatomic)NSTimer* updateTimer;
@end
@implementation ChatViewController
#pragma mark - View lifecycle
/**
* Override point for customization.
*
* Customize your view.
* Look at the properties on `JSQMessagesViewController` and `JSQMessagesCollectionView` to see what is possible.
*
* Customize your layout.
* Look at the properties on `JSQMessagesCollectionViewFlowLayout` to see what is possible.
*/
- (void)viewDidLoad
{
NSLog(@"CHAT VIEW DID LOAD");
[super viewDidLoad];
User* me = ([AuthenticationHelper sharedInstance]).loggedInUser;
self.title = @"JSQMessages";
self.messagesArray = [[NSMutableArray alloc] init];
self.senderId = [NSString stringWithFormat:@"%i", [me.id intValue]];
self.senderDisplayName = me.fullName;
self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero;
self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero;
self.showLoadEarlierMessagesHeader = NO;
self.inputToolbar.contentView.leftBarButtonItem = nil;
self.inputToolbar.maximumHeight = 150;
//TODO: create scheduled task to pull messages from the server.
}
-(void)updateMessages {
[Chat getChatsForId:self.user.id SuccessCallback:^(NSArray* chats){
self.messagesArray = [chats mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
} errorCallback:^(NSString* error) {
}];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval: 5.0 target: self
selector: @selector(updateMessages) userInfo: nil repeats: YES];
[self.updateTimer fire];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.collectionView.collectionViewLayout.springinessEnabled = NO;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.updateTimer = nil;
}
#pragma mark - JSQMessagesViewController method overrides
- (void)didPressSendButton:(UIButton *)button
withMessageText:(NSString *)text
senderId:(NSString *)senderId
senderDisplayName:(NSString *)senderDisplayName
date:(NSDate *)date
{
NSLog(@"SENDER ID IS %@", senderId);
NSLog(@"SENDER DISPLAY NAME IS %@", senderDisplayName);
NSLog(@"DATE IS %@", date.description);
[Chat createChat:@{@"to_user_id" : self.user.id, @"from_user_id" : ([AuthenticationHelper sharedInstance]).loggedInUser.id, @"message" : text} successCallback:^(Chat* chat) {
[self.messagesArray addObject:chat];
dispatch_async(dispatch_get_main_queue(), ^{
[self finishSendingMessageAnimated:YES];
});
} errorCallback:^(NSString* error) {
}];
}
#pragma mark - JSQMessages CollectionView DataSource
- (id<JSQMessageData>)collectionView:(JSQMessagesCollectionView *)collectionView messageDataForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [self.messagesArray objectAtIndex:indexPath.item];
}
- (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.
*/
Chat* chat = [self.messagesArray objectAtIndex:indexPath.item];
if ([chat.toUserId intValue] == [self.user.id intValue]) {
return [[[JSQMessagesBubbleImageFactory alloc] init] outgoingMessagesBubbleImageWithColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"tableBackground.png"]]];
}
return [[[JSQMessagesBubbleImageFactory alloc] init] incomingMessagesBubbleImageWithColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"TopNavBarBackground.png"]]];
}
- (id<JSQMessageAvatarImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath
{
/**
* This logic should be consistent with what you return from `heightForCellTopLabelAtIndexPath:`
* The other label text delegate methods should follow a similar pattern.
*
* Show a timestamp for every 3rd message
*/
if (indexPath.item % 3 == 0) {
JSQMessage *message = [self.messagesArray objectAtIndex:indexPath.item];
return [[JSQMessagesTimestampFormatter sharedFormatter] attributedTimestampForDate:message.date];
}
return nil;
}
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath
{
JSQMessage *message = [self.messagesArray objectAtIndex:indexPath.item];
if ([message.senderId isEqualToString:self.senderId]) {
return nil;
}
if (indexPath.item - 1 > 0) {
JSQMessage *previousMessage = [self.messagesArray objectAtIndex:indexPath.item - 1];
if ([[previousMessage senderId] isEqualToString:message.senderId]) {
return nil;
}
}
return [[NSAttributedString alloc] initWithString:message.senderDisplayName];
}
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
#pragma mark - UICollectionView DataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.messagesArray count];
}
- (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
/**
* Override point for customizing cells
*/
JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath];
/**
* Configure almost *anything* on the cell
*
* Text colors, label text, label colors, etc.
*
*
* DO NOT set `cell.textView.font` !
* Instead, you need to set `self.collectionView.collectionViewLayout.messageBubbleFont` to the font you want in `viewDidLoad`
*
*
* DO NOT manipulate cell layout information!
* Instead, override the properties you want on `self.collectionView.collectionViewLayout` from `viewDidLoad`
*/
/*
JSQMessage *msg = [self.demoData.messages objectAtIndex:indexPath.item];
if (!msg.isMediaMessage) {
if ([msg.senderId isEqualToString:self.senderId]) {
cell.textView.textColor = [UIColor blackColor];
}
else {
cell.textView.textColor = [UIColor whiteColor];
}
cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : cell.textView.textColor,
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) };
}
*/
// cell.textView.text = ((Chat*)[self.messagesArray objectAtIndex:indexPath.row]).message;
return cell;
}
#pragma mark - Adjusting cell label heights
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath
{
/**
* Each label in a cell has a `height` delegate method that corresponds to its text dataSource method
*/
/**
* This logic should be consistent with what you return from `attributedTextForCellTopLabelAtIndexPath:`
* The other label height delegate methods should follow similarly
*
* Show a timestamp for every 3rd message
*/
if (indexPath.item % 3 == 0) {
return kJSQMessagesCollectionViewCellLabelHeightDefault;
}
return 0.0f;
}
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath
{
/**
* iOS7-style sender name labels
*/
JSQMessage *currentMessage = [self.messagesArray objectAtIndex:indexPath.item];
if ([[currentMessage senderId] isEqualToString:self.senderId]) {
return 0.0f;
}
if (indexPath.item - 1 > 0) {
JSQMessage *previousMessage = [self.messagesArray objectAtIndex:indexPath.item - 1];
if ([[previousMessage senderId] isEqualToString:[currentMessage senderId]]) {
return 0.0f;
}
}
return kJSQMessagesCollectionViewCellLabelHeightDefault;
}
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath
{
return 0.0f;
}
#pragma mark - Responding to collection view tap events
- (void)collectionView:(JSQMessagesCollectionView *)collectionView
header:(JSQMessagesLoadEarlierHeaderView *)headerView didTapLoadEarlierMessagesButton:(UIButton *)sender
{
NSLog(@"Load earlier messages!");
}
- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapAvatarImageView:(UIImageView *)avatarImageView atIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Tapped avatar!");
}
- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Tapped message bubble!");
}
- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCellAtIndexPath:(NSIndexPath *)indexPath touchLocation:(CGPoint)touchLocation
{
NSLog(@"Tapped cell at %@!", NSStringFromCGPoint(touchLocation));
}
@end
I adjusted the cell label heights just like the demo did, but the text keeps getting cut off because the bubbles are not being resized. Is it the way I'm using my Chat
object instead of the JSQMessage
object?
Any help or advice would be greatly appreciated, because I've been stuck on this one for a while. Thanks.