Skip to content
This repository has been archived by the owner on Aug 14, 2019. It is now read-only.

Commit

Permalink
Implement new bubbleSizeCalculator. close #438 and #347. Changes will…
Browse files Browse the repository at this point in the history
… affect #1112 and #1113.
  • Loading branch information
jessesquires committed Aug 13, 2015
1 parent 679ac60 commit 983fb58
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 82 deletions.
8 changes: 8 additions & 0 deletions JSQMessages.xcodeproj/project.pbxproj
Expand Up @@ -101,6 +101,7 @@
88A2601919D8E18400924534 /* JSQMessagesTypingIndicatorFooterViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A2600019D8E18400924534 /* JSQMessagesTypingIndicatorFooterViewTests.m */; };
88A2601B19D8E45600924534 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 88A2601A19D8E45600924534 /* Info.plist */; };
88A901B619F618B100F99777 /* JSQMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A901B519F618B100F99777 /* JSQMediaItem.m */; };
88B5C41F1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B5C41E1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m */; };
88C00A4E1A44D4C600B004B3 /* JSQLocationMediaItemTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C00A4D1A44D4C600B004B3 /* JSQLocationMediaItemTests.m */; };
88C00A501A44D4D800B004B3 /* JSQPhotoMediaItemTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C00A4F1A44D4D800B004B3 /* JSQPhotoMediaItemTests.m */; };
88C00A521A44D4E500B004B3 /* JSQVideoMediaItemTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C00A511A44D4E500B004B3 /* JSQVideoMediaItemTests.m */; };
Expand Down Expand Up @@ -260,6 +261,9 @@
88A2601A19D8E45600924534 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
88A901B419F618B100F99777 /* JSQMediaItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMediaItem.h; sourceTree = "<group>"; };
88A901B519F618B100F99777 /* JSQMediaItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMediaItem.m; sourceTree = "<group>"; };
88B5C41D1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubblesSizeCalculator.h; sourceTree = "<group>"; };
88B5C41E1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubblesSizeCalculator.m; sourceTree = "<group>"; };
88B5C4201B7C424700EC79D4 /* JSQMessagesBubbleSizeCalculating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubbleSizeCalculating.h; sourceTree = "<group>"; };
88C00A4D1A44D4C600B004B3 /* JSQLocationMediaItemTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQLocationMediaItemTests.m; sourceTree = "<group>"; };
88C00A4F1A44D4D800B004B3 /* JSQPhotoMediaItemTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQPhotoMediaItemTests.m; sourceTree = "<group>"; };
88C00A511A44D4E500B004B3 /* JSQVideoMediaItemTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQVideoMediaItemTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -473,6 +477,9 @@
88A25F6F19D8E01A00924534 /* Layout */ = {
isa = PBXGroup;
children = (
88B5C4201B7C424700EC79D4 /* JSQMessagesBubbleSizeCalculating.h */,
88B5C41D1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.h */,
88B5C41E1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m */,
88A25F7019D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.h */,
88A25F7119D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.m */,
88A25F7219D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.h */,
Expand Down Expand Up @@ -832,6 +839,7 @@
88A25F3C19D8DF2500924534 /* main.m in Sources */,
88A25F3719D8DF2500924534 /* AppDelegate.m in Sources */,
886FFD2E19E9A65D00EB8485 /* UIDevice+JSQMessages.m in Sources */,
88B5C41F1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m in Sources */,
88A25FB619D8E01A00924534 /* NSString+JSQMessages.m in Sources */,
88A901B619F618B100F99777 /* JSQMediaItem.m in Sources */,
88A25FCC19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.m in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions JSQMessagesViewController/JSQMessages.h
Expand Up @@ -29,6 +29,8 @@
#import "JSQMessagesLoadEarlierHeaderView.h"

// Layout
#import "JSQMessagesBubbleSizeCalculating.h"
#import "JSQMessagesBubblesSizeCalculator.h"
#import "JSQMessagesCollectionViewFlowLayout.h"
#import "JSQMessagesCollectionViewLayoutAttributes.h"
#import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h"
Expand Down
@@ -0,0 +1,57 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class JSQMessagesCollectionViewFlowLayout;
@protocol JSQMessageData;

/**
* The `JSQMessagesBubbleSizeCalculating` protocol defines the common interface through which
* an object provides layout information to an instance of `JSQMessagesCollectionViewFlowLayout`.
*
* A concrete class that conforms to this protocol is provided in the library.
* See `JSQMessagesBubbleSizeCalculator`.
*/
@protocol JSQMessagesBubbleSizeCalculating <NSObject>

/**
* Computes and returns the size of the `messageBubbleImageView` property
* of a `JSQMessagesCollectionViewCell` for the specified messageData at indexPath.
*
* @param messageData A message data object.
* @param indexPath The index path at which messageData is located.
* @param layout The layout object asking for this information.
*
* @return A sizes that specifies the required dimensions to display the entire message contents.
* Note, this is *not* the entire cell, but only its message bubble.
*/
- (CGSize)messageBubbleSizeForMessageData:(id<JSQMessageData>)messageData
atIndexPath:(NSIndexPath *)indexPath
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout;

/**
* Notifies the receiver that the layout will be reset.
* Use this method to clear any cached layout information, if necessary.
*
* @param layout The layout object notifying the receiver.
*/
- (void)prepareForResettingLayout:(JSQMessagesCollectionViewFlowLayout *)layout;

@end
@@ -0,0 +1,40 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//

#import <Foundation/Foundation.h>

#import "JSQMessagesBubbleSizeCalculating.h"

/**
* An instance of `JSQMessagesBubblesSizeCalculator` is responsible for calculating
* message bubble sizes for an instance of `JSQMessagesCollectionViewFlowLayout`.
*/
@interface JSQMessagesBubblesSizeCalculator : NSObject <JSQMessagesBubbleSizeCalculating>

/**
* Initializes and returns a bubble size calculator with the given cache and minimumBubbleWidth.
*
* @param cache A cache object used to store layout information.
* @param minimumBubbleWidth The minimum width for any given message bubble.
*
* @return An initialized `JSQMessagesBubblesSizeCalculator` object if successful, `nil` otherwise.
*/
- (instancetype)initWithCache:(NSCache *)cache
minimumBubbleWidth:(NSUInteger)minimumBubbleWidth NS_DESIGNATED_INITIALIZER;

@end
141 changes: 141 additions & 0 deletions JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.m
@@ -0,0 +1,141 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//

#import "JSQMessagesBubblesSizeCalculator.h"

#import "JSQMessagesCollectionView.h"
#import "JSQMessagesCollectionViewDataSource.h"
#import "JSQMessagesCollectionViewFlowLayout.h"
#import "JSQMessageData.h"

#import "UIImage+JSQMessages.h"


@interface JSQMessagesBubblesSizeCalculator ()

@property (strong, nonatomic, readonly) NSCache *cache;

@property (assign, nonatomic, readonly) NSUInteger minimumBubbleWidth;

@end


@implementation JSQMessagesBubblesSizeCalculator

#pragma mark - Init

- (instancetype)initWithCache:(NSCache *)cache minimumBubbleWidth:(NSUInteger)minimumBubbleWidth
{
NSParameterAssert(cache != nil);
NSParameterAssert(minimumBubbleWidth > 0);

self = [super init];
if (self) {
_cache = cache;
_minimumBubbleWidth = minimumBubbleWidth;
}
return self;
}

- (instancetype)init
{
NSCache *cache = [NSCache new];
cache.name = @"JSQMessagesBubblesSizeCalculator.cache";
cache.countLimit = 200;
return [self initWithCache:cache
minimumBubbleWidth:[UIImage jsq_bubbleCompactImage].size.width];
}

#pragma mark - NSObject

- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: cache=%@, minimumBubbleWidth=%@>",
[self class], self.cache, @(self.minimumBubbleWidth)];
}

#pragma mark - JSQMessagesBubbleSizeCalculating

- (void)prepareForResettingLayout:(JSQMessagesCollectionViewFlowLayout *)layout
{
[self.cache removeAllObjects];
}

- (CGSize)messageBubbleSizeForMessageData:(id<JSQMessageData>)messageData
atIndexPath:(NSIndexPath *)indexPath
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
{
NSValue *cachedSize = [self.cache objectForKey:@([messageData messageHash])];
if (cachedSize != nil) {
return [cachedSize CGSizeValue];
}

CGSize finalSize = CGSizeZero;

if ([messageData isMediaMessage]) {
finalSize = [[messageData media] mediaViewDisplaySize];
}
else {
CGSize avatarSize = [self jsq_avatarSizeForMessageData:messageData withLayout:layout];

// from the cell xibs, there is a 2 point space between avatar and bubble
CGFloat spacingBetweenAvatarAndBubble = 2.0f;
CGFloat horizontalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.left + layout.messageBubbleTextViewTextContainerInsets.right;
CGFloat horizontalFrameInsets = layout.messageBubbleTextViewFrameInsets.left + layout.messageBubbleTextViewFrameInsets.right;

CGFloat horizontalInsetsTotal = horizontalContainerInsets + horizontalFrameInsets + spacingBetweenAvatarAndBubble;
CGFloat maximumTextWidth = layout.itemWidth - avatarSize.width - layout.messageBubbleLeftRightMargin - horizontalInsetsTotal;

CGRect stringRect = [[messageData text] boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:@{ NSFontAttributeName : layout.messageBubbleFont }
context:nil];

CGSize stringSize = CGRectIntegral(stringRect).size;

CGFloat verticalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.top + layout.messageBubbleTextViewTextContainerInsets.bottom;
CGFloat verticalFrameInsets = layout.messageBubbleTextViewFrameInsets.top + layout.messageBubbleTextViewFrameInsets.bottom;

// add extra 2 points of space, because `boundingRectWithSize:` is slightly off
// not sure why. magix. (shrug) if you know, submit a PR
CGFloat verticalInsets = verticalContainerInsets + verticalFrameInsets + 2.0f;

// same as above, an extra 2 points of magix
CGFloat finalWidth = MAX(stringSize.width + horizontalInsetsTotal, self.minimumBubbleWidth) + 2.0f;

finalSize = CGSizeMake(finalWidth, stringSize.height + verticalInsets);
}

[self.cache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageData messageHash])];

return finalSize;
}

- (CGSize)jsq_avatarSizeForMessageData:(id<JSQMessageData>)messageData
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
{
NSString *messageSender = [messageData senderId];

if ([messageSender isEqualToString:[layout.collectionView.dataSource senderId]]) {
return layout.outgoingAvatarViewSize;
}

return layout.incomingAvatarViewSize;
}

@end
Expand Up @@ -23,6 +23,8 @@

#import <UIKit/UIKit.h>

#import "JSQMessagesBubbleSizeCalculating.h"

@class JSQMessagesCollectionView;


Expand Down Expand Up @@ -63,6 +65,12 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault;
@property (readonly, nonatomic) JSQMessagesCollectionView *collectionView;
#pragma clang diagnostic pop

/**
* The object that the layout uses to calculate bubble sizes.
* The default value is an instance of `JSQMessagesBubblesSizeCalculator`.
*/
@property (strong, nonatomic) id<JSQMessagesBubbleSizeCalculating> bubbleSizeCalculator;

/**
* Specifies whether or not the layout should enable spring behavior dynamics for its items using `UIDynamics`.
*
Expand Down Expand Up @@ -181,12 +189,15 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault;

/**
* Computes and returns the size of the `messageBubbleImageView` property of a `JSQMessagesCollectionViewCell`
* at the specified indexPath. The returned size contains the required dimensions to display the entire message contents.
* Note, this is *not* the entire cell, but only its message bubble.
* at the specified indexPath.
*
* @param indexPath The index path of the item to be displayed.
*
* @return The size of the message bubble for the item displayed at indexPath.
*
* @discussion The layout uses its `bubbleSizeCalculator` object to perform this computation.
* The returned size contains the required dimensions to display the entire message contents.
* Note, this is *not* the entire cell, but only its message bubble.
*/
- (CGSize)messageBubbleSizeForItemAtIndexPath:(NSIndexPath *)indexPath;

Expand Down

0 comments on commit 983fb58

Please sign in to comment.