├── Source ├── Resources │ └── Images │ │ ├── camera_button.png │ │ ├── camera_button@2x.png │ │ └── camera_button@3x.png ├── Components │ └── Photos │ │ ├── Assets │ │ ├── checkmark_icon.png │ │ ├── checkmark_icon@2x.png │ │ └── checkmark_icon@3x.png │ │ ├── DBMessagingPhotoPickerPresentationController.h │ │ ├── DBMessagingPhotoPickerController.h │ │ ├── DBMessagingPhotoPickerPresentationController.m │ │ └── DBMessagingPhotoPickerController.xib ├── Views │ ├── DBMessagingCellTextView.h │ ├── DBMessagingVideoMediaCell.h │ ├── DBMessagingTypingIndicatorFooterView.h │ ├── DBMessagingImageMediaCell.h │ ├── DBMessagingLoadEarlierMessagesHeaderView.h │ ├── DBMessagingLocationMediaCell.h │ ├── DBMessagingLocationPin.h │ ├── DBMessagingMediaCell.h │ ├── DBMessagingTimestampSupplementaryView.h │ ├── DBMessagingTextCell.h │ ├── DBMessagingLocationPin.m │ ├── DBMessagingVideoMediaCell.m │ ├── DBMessagingImageMediaCell.m │ ├── DBMessagingInputTextView.h │ ├── DBMessagingLoadEarlierMessagesHeaderView.m │ ├── DBMessagingInputToolbar.h │ ├── DBMessagingCollectionView.h │ ├── DBMessagingParentCell.h │ ├── DBMessagingLocationMediaCell.m │ ├── DBMessagingCellTextView.m │ ├── DBMessagingTypingIndicatorFooterView.m │ ├── DBMessagingTimestampSupplementaryView.m │ ├── DBMessagingMediaCell.m │ ├── DBMessagingCollectionView.m │ ├── DBMessagingInputToolbar.m │ └── DBMessagingTextCell.m ├── Categories │ ├── NSMutableAttributedString+Messaging.h │ ├── NSMutableAttributedString+Messaging.m │ ├── NSAttributedString+Messaging.m │ ├── NSAttributedString+Messaging.h │ ├── UIImage+Messaging.h │ ├── UIColor+Messaging.h │ ├── UIImage+AnimatedGIF.h │ ├── UIColor+Messaging.m │ ├── UIImage+Messaging.m │ └── UIImage+AnimatedGIF.m ├── Controllers │ ├── DBSystemSoundPlayer.h │ ├── DBSystemSoundPlayer.m │ ├── DBInteractiveKeyboardController.h │ ├── DBMessageBubbleController.h │ └── DBMessagingViewController.h ├── Protocols │ ├── DBMessagingCollectionViewDelegateFlowLayout.h │ ├── DBMessagingCollectionViewDelegate.h │ └── DBMessagingCollectionViewDataSource.h ├── DBMessagingKitConstants.h ├── Layout │ ├── DBMessagingCollectionViewSlidingTimestampFlowLayout.h │ ├── DBMessagingCollectionViewFlowLayoutInvalidationContext.m │ ├── DBMessagingCollectionViewFlowLayoutInvalidationContext.h │ ├── DBMessagingCollectionViewHiddenTimestampFlowLayout.h │ ├── DBMessagingCollectionViewLayoutAttributes.h │ ├── DBMessagingCollectionViewLayoutAttributes.m │ ├── DBMessagingCollectionViewBaseFlowLayout.h │ ├── DBMessagingCollectionViewHiddenTimestampFlowLayout.m │ └── DBMessagingCollectionViewSlidingTimestampFlowLayout.m ├── Factories │ ├── DBMessageBubbleFactory.h │ ├── DBMessageBubbleFactory.m │ ├── DBMessagingTimestampFormatter.h │ └── DBMessagingTimestampFormatter.m └── DBMessagingKit.h ├── MessagingKit ├── Images.xcassets │ ├── location_icon.imageset │ │ ├── location_icon.png │ │ ├── location_icon@2x.png │ │ ├── location_icon@3x.png │ │ └── Contents.json │ ├── MessageBubbleMid.imageset │ │ ├── MessageBubbleMid.png │ │ ├── MessageBubbleMid@2x.png │ │ ├── MessageBubbleMid@3x.png │ │ └── Contents.json │ ├── MessageBubbleTop.imageset │ │ ├── MessageBubbleTop.png │ │ ├── MessageBubbleTop@2x.png │ │ ├── MessageBubbleTop@3x.png │ │ └── Contents.json │ ├── MessageBubbleBottom.imageset │ │ ├── MessageBubbleBottom.png │ │ ├── MessageBubbleBottom@2x.png │ │ ├── MessageBubbleBottom@3x.png │ │ └── Contents.json │ ├── MessageBubbleDefault.imageset │ │ ├── MessageBubbleDefault.png │ │ ├── MessageBubbleDefault@2x.png │ │ ├── MessageBubbleDefault@3x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── main.m ├── AppDelegate.h ├── Info.plist ├── AppDelegate.m └── Base.lproj │ └── LaunchScreen.xib ├── DBMessagingKit.xcodeproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Demo ├── ViewController.h └── Model │ ├── Message.m │ └── Message.h ├── .gitignore ├── MessagingKitTests ├── Info.plist └── DBMessagingKitTests.m ├── LICENSE └── README.md /Source/Resources/Images/camera_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/Source/Resources/Images/camera_button.png -------------------------------------------------------------------------------- /Source/Resources/Images/camera_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/Source/Resources/Images/camera_button@2x.png -------------------------------------------------------------------------------- /Source/Resources/Images/camera_button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/Source/Resources/Images/camera_button@3x.png -------------------------------------------------------------------------------- /Source/Components/Photos/Assets/checkmark_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/Source/Components/Photos/Assets/checkmark_icon.png -------------------------------------------------------------------------------- /Source/Components/Photos/Assets/checkmark_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/Source/Components/Photos/Assets/checkmark_icon@2x.png -------------------------------------------------------------------------------- /Source/Components/Photos/Assets/checkmark_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/Source/Components/Photos/Assets/checkmark_icon@3x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/location_icon.imageset/location_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/location_icon.imageset/location_icon.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/location_icon.imageset/location_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/location_icon.imageset/location_icon@2x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/location_icon.imageset/location_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/location_icon.imageset/location_icon@3x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleMid.imageset/MessageBubbleMid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleMid.imageset/MessageBubbleMid.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleTop.imageset/MessageBubbleTop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleTop.imageset/MessageBubbleTop.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleMid.imageset/MessageBubbleMid@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleMid.imageset/MessageBubbleMid@2x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleMid.imageset/MessageBubbleMid@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleMid.imageset/MessageBubbleMid@3x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleTop.imageset/MessageBubbleTop@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleTop.imageset/MessageBubbleTop@2x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleTop.imageset/MessageBubbleTop@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleTop.imageset/MessageBubbleTop@3x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleBottom.imageset/MessageBubbleBottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleBottom.imageset/MessageBubbleBottom.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleDefault.imageset/MessageBubbleDefault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleDefault.imageset/MessageBubbleDefault.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleBottom.imageset/MessageBubbleBottom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleBottom.imageset/MessageBubbleBottom@2x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleBottom.imageset/MessageBubbleBottom@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleBottom.imageset/MessageBubbleBottom@3x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleDefault.imageset/MessageBubbleDefault@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleDefault.imageset/MessageBubbleDefault@2x.png -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleDefault.imageset/MessageBubbleDefault@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devonboyer/DBMessagingKit/HEAD/MessagingKit/Images.xcassets/MessageBubbleDefault.imageset/MessageBubbleDefault@3x.png -------------------------------------------------------------------------------- /DBMessagingKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MessagingKit/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MessagingKit 4 | // 5 | // Created by Devon Boyer on 2014-12-04. 6 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingCellTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCellTextView.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-11-30. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface DBMessagingCellTextView : UITextView 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingVideoMediaCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingVideoMediaCell.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-09. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingMediaCell.h" 16 | 17 | @interface DBMessagingVideoMediaCell : DBMessagingMediaCell 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Demo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // MessagingKit 4 | // 5 | // 6 | // GitHub 7 | // https://github.com/DevonBoyer/DBMessagingKit 8 | // 9 | // 10 | // Created by Devon Boyer on 2014-12-04. 11 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 12 | // 13 | // Released under an MIT license: http://opensource.org/licenses/MIT 14 | // 15 | 16 | #import 17 | #import "DBMessagingKit.h" 18 | 19 | @interface ViewController : DBMessagingViewController 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /MessagingKit/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-12-04. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface AppDelegate : UIResponder 18 | 19 | @property (strong, nonatomic) UIWindow *window; 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/location_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "location_icon.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "location_icon@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "location_icon@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Source/Components/Photos/DBMessagingPhotoPickerPresentationController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingPhotoPickerPresentationController.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-10. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface DBMessagingPhotoPickerPresentationController : UIPresentationController 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleMid.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "MessageBubbleMid.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "MessageBubbleMid@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "MessageBubbleMid@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleTop.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "MessageBubbleTop.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "MessageBubbleTop@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "MessageBubbleTop@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Source/Views/DBMessagingTypingIndicatorFooterView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingTypingIndicatorFooterView.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-23. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface DBMessagingTypingIndicatorFooterView : UICollectionReusableView 18 | 19 | + (NSString *)viewReuseIdentifier; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleBottom.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "MessageBubbleBottom.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "MessageBubbleBottom@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "MessageBubbleBottom@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Source/Views/DBMessagingImageMediaCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingImageMediaCell.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-09. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingMediaCell.h" 16 | 17 | @interface DBMessagingImageMediaCell : DBMessagingMediaCell 18 | 19 | @property (strong, nonatomic, readonly) UIImageView *imageView; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/MessageBubbleDefault.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "MessageBubbleDefault.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "MessageBubbleDefault@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "MessageBubbleDefault@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /Demo/Model/Message.m: -------------------------------------------------------------------------------- 1 | // 2 | // Message.m 3 | // MessagingKit 4 | // 5 | // Created by Devon Boyer on 2014-09-17. 6 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 7 | // 8 | 9 | #import "Message.h" 10 | 11 | @implementation Message 12 | 13 | - (instancetype)initWithValue:(id)value mime:(NSString *)mime sentByUserID:(NSString *)sentByUserID sentAt:(NSDate *)sentAt { 14 | self = [self init]; 15 | if (self) { 16 | _sentByUserID = sentByUserID; 17 | _sentAt = sentAt; 18 | _mime = mime; 19 | _value = value; 20 | } 21 | return self; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Source/Categories/NSMutableAttributedString+Messaging.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+Messaging.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-08. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface NSMutableAttributedString (Messaging) 18 | 19 | + (NSMutableAttributedString *)mutableAttributedStringWithAttachment:(NSTextAttachment *)attatchment; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingLoadEarlierMessagesHeaderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingLoadEarlierMessagesHeaderView.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-26. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface DBMessagingLoadEarlierMessagesHeaderView : UICollectionReusableView 18 | 19 | + (NSString *)viewReuseIdentifier; 20 | + (CGFloat)heightForHeader; 21 | 22 | - (void)startAnimating; 23 | - (void)stopAnimating; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingLocationMediaCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingLocationMediaCell.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-09. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingMediaCell.h" 16 | 17 | #import 18 | 19 | @class MKMapView; 20 | 21 | @interface DBMessagingLocationMediaCell : DBMessagingMediaCell 22 | 23 | @property (strong, nonatomic, readonly) MKMapView *mapView; 24 | 25 | @property (assign, nonatomic) CLLocationCoordinate2D coordinate; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingLocationPin.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingLocationPin.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-10. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | #import 18 | 19 | @interface DBMessagingLocationPin : NSObject 20 | 21 | + (NSString *)annotationReuseIdentifier; 22 | 23 | - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate; 24 | 25 | @property (nonatomic, assign) CLLocationCoordinate2D coordinate; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Demo/Model/Message.h: -------------------------------------------------------------------------------- 1 | // 2 | // Message.h 3 | // MessagingKit 4 | // 5 | // Created by Devon Boyer on 2014-09-17. 6 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "DBMessagingKitConstants.h" 13 | 14 | @interface Message : NSObject 15 | 16 | @property (strong, nonatomic) NSString *sentByUserID; 17 | @property (strong, nonatomic) NSDate *sentAt; 18 | @property (assign, nonatomic) NSString *mime; 19 | @property (strong, nonatomic) NSData *value; 20 | 21 | - (instancetype)initWithValue:(id)value 22 | mime:(NSString *)mime 23 | sentByUserID:(NSString *)sentByUserID 24 | sentAt:(NSDate *)sentAt; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Source/Views/DBMessagingMediaCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingMediaCell.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-09. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingParentCell.h" 16 | 17 | @protocol DBMessagingMediaCellDelegate 18 | 19 | @optional 20 | - (void)messageCell:(DBMessagingParentCell *)cell didTapMediaView:(UIView *)mediaView; 21 | 22 | @end 23 | 24 | @interface DBMessagingMediaCell : DBMessagingParentCell 25 | 26 | @property (weak, nonatomic) id delegate; 27 | 28 | @property (strong, nonatomic) UIView *mediaView; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Source/Categories/NSMutableAttributedString+Messaging.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+Messaging.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-08. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "NSMutableAttributedString+Messaging.h" 16 | 17 | @implementation NSMutableAttributedString (Messaging) 18 | 19 | + (NSMutableAttributedString *)mutableAttributedStringWithAttachment:(NSTextAttachment *)attatchment { 20 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithAttachment:attatchment]; 21 | return [[NSMutableAttributedString alloc] initWithAttributedString:attributedString]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingTimestampSupplementaryView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingTimestampSupplementaryView.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-11. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | #import "DBMessagingParentCell.h" 17 | #import "DBMessagingKitConstants.h" 18 | 19 | @interface DBMessagingTimestampSupplementaryView : UICollectionReusableView 20 | 21 | + (NSString *)viewReuseIdentifier; 22 | 23 | @property (strong, nonatomic, readonly) UILabel *timestampLabel; 24 | @property (assign, nonatomic) MessageBubbleType type; 25 | @property (assign, nonatomic) DBMessagingTimestampStyle timestampStyle; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /MessagingKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.self.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Source/Controllers/DBSystemSoundPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBSystemSoundPlayer.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/MessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-28. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | /** 18 | * An instance of 'DBSystemSoundPlayer' allows for the playing simple sounds, such as when sending 19 | * or recieving a message. 20 | */ 21 | @interface DBSystemSoundPlayer : NSObject 22 | 23 | + (DBSystemSoundPlayer *)sharedPlayer; 24 | 25 | - (void)playSoundWithName:(NSString *)filename fileExtension:(NSString *)extension; 26 | - (void)playAlertSoundWithName:(NSString *)filename fileExtension:(NSString *)extension; 27 | - (void)playVibrateSound; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingTextCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingTextCell.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-10. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingParentCell.h" 16 | 17 | @protocol DBMessagingTextCellDelegate 18 | 19 | @optional 20 | - (void)messageCell:(DBMessagingParentCell *)cell didTapMessageBubbleImageView:(UIImageView *)messageBubbleImageView; 21 | 22 | @end 23 | 24 | @interface DBMessagingTextCell : DBMessagingParentCell 25 | 26 | 27 | @property (weak, nonatomic) id delegate; 28 | 29 | @property (strong, nonatomic, readonly) UITextView *messageTextView; 30 | @property (strong, nonatomic) NSString *messageText; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Source/Categories/NSAttributedString+Messaging.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+Messaging.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-23. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "NSAttributedString+Messaging.h" 16 | 17 | @implementation NSAttributedString (Messaging) 18 | 19 | + (CGSize)boundingBoxForAttributedString:(NSAttributedString *)attributedString maxWidth:(CGFloat)maxWidth { 20 | return [attributedString boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size; 21 | } 22 | 23 | + (NSAttributedString *)newlineAttributedString { 24 | return [[NSAttributedString alloc] initWithString:@"\n"]; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Source/Protocols/DBMessagingCollectionViewDelegateFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewDelegateFlowLayout.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-12. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @class DBMessagingCollectionViewBaseFlowLayout; 18 | 19 | /** 20 | * The 'DBMessagingCollectionViewDelegateFlowLayout' protocol defines methods that allow you to 21 | * manage additional layout information for the collection view. 22 | */ 23 | @protocol DBMessagingCollectionViewDelegateFlowLayout 24 | 25 | @optional 26 | 27 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(DBMessagingCollectionViewBaseFlowLayout *)collectionViewLayout referenceSizeForMediaViewAtIndexPath:(NSIndexPath *)indexPath; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Source/Categories/NSAttributedString+Messaging.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+Messaging.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-23. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface NSAttributedString (Messaging) 18 | 19 | /** 20 | * Calculates and returns the size of the bounding box of an attributed string that fits the 21 | * speficied maxWidth. 22 | * 23 | * @param attributedString The attributed string. 24 | * @param maxWidth The maximum width for the bounding box that contains with attributed string. 25 | * 26 | * @return The size of the bounding box that fits the specified maxWidth. 27 | */ 28 | + (CGSize)boundingBoxForAttributedString:(NSAttributedString *)attributedString maxWidth:(CGFloat)maxWidth; 29 | 30 | + (NSAttributedString *)newlineAttributedString; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /MessagingKitTests/DBMessagingKitTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingKitTests.m 3 | // DBMessagingKitTests 4 | // 5 | // Created by Devon Boyer on 2014-12-04. 6 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface DBMessagingKitTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation DBMessagingKitTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Source/Categories/UIImage+Messaging.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Messaging.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-18. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface UIImage (Messaging) 18 | 19 | // Base64 Encoding/Decoding 20 | 21 | - (NSString *)encodeToBase64String; 22 | 23 | + (UIImage *)decodeBase64StringToImage:(NSString *)encodedString; 24 | 25 | 26 | // Image Manipulation 27 | 28 | + (UIImage *)imageWithColor:(UIColor *)color; 29 | 30 | + (UIImage *)imageByRoundingCorners:(CGFloat)cornerRadius ofImage:(UIImage *)image; 31 | 32 | 33 | /** 34 | * Creates and returns a new image overlayed with the specified color. 35 | * 36 | * @param color The color to overlay the receiver. 37 | * 38 | * @return A new image overlayed with the specified color. 39 | */ 40 | - (UIImage *)imageOverlayedWithColor:(UIColor *)color; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Source/DBMessagingKitConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingKitConstants.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-30. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | /** 18 | * The key to access the mime type in a message part. 19 | */ 20 | static NSString *const DBMessagePartMIMEKey = @"mime"; 21 | 22 | /** 23 | * The key to access the value in a message part. 24 | */ 25 | static NSString *const DBMessagePartValueKey = @"value"; 26 | 27 | /** 28 | * Specifys the type of layout in which to display timestamps. 29 | * 30 | * @see DBMessagingCollectionViewHiddenTimestampFlowLayout 31 | * @see DBMessagingCollectionViewSlidingTimestampFlowLayout 32 | */ 33 | typedef NS_ENUM(NSUInteger, DBMessagingTimestampStyle) { 34 | DBMessagingTimestampStyleNone, 35 | DBMessagingTimestampStyleHidden, 36 | DBMessagingTimestampStyleSliding 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) {{{year}}} {{{fullname}}} 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingLocationPin.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingLocationPin.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-10. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingLocationPin.h" 16 | 17 | #import 18 | 19 | @interface DBMessagingLocationPin () 20 | 21 | @end 22 | 23 | @implementation DBMessagingLocationPin 24 | 25 | + (NSString *)annotationReuseIdentifier { 26 | return NSStringFromClass([self class]); 27 | } 28 | 29 | - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate { 30 | self = [super init]; 31 | if (self) { 32 | _coordinate = coordinate; 33 | } 34 | return self; 35 | } 36 | 37 | - (MKMapItem*)mapItem { 38 | 39 | MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:_coordinate addressDictionary:nil]; 40 | 41 | MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:placemark]; 42 | mapItem.name = self.title; 43 | 44 | return mapItem; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingVideoMediaCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingVideoMediaCell.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-09. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingVideoMediaCell.h" 16 | 17 | static NSString *kDBMessagingVideoMediaCellMimeType = @"video/mp4"; 18 | 19 | @interface DBMessagingVideoMediaCell () 20 | 21 | @end 22 | 23 | @implementation DBMessagingVideoMediaCell 24 | 25 | + (NSString *)mimeType { 26 | return kDBMessagingVideoMediaCellMimeType; 27 | } 28 | 29 | + (void)setMimeType:(NSString *)mimeType { 30 | NSAssert(![mimeType isEqualToString:@""] || mimeType != nil, @"Mime type for class %@ cannot be nil.", [self class]); 31 | kDBMessagingVideoMediaCellMimeType = mimeType; 32 | } 33 | 34 | + (NSString *)cellReuseIdentifier { 35 | return NSStringFromClass([self class]); 36 | } 37 | 38 | - (instancetype)initWithFrame:(CGRect)frame 39 | { 40 | self = [super initWithFrame:frame]; 41 | if (self) { 42 | 43 | } 44 | return self; 45 | } 46 | 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewSlidingTimestampFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewSlidingTimestampFlowLayout.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-08. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingCollectionViewBaseFlowLayout.h" 16 | 17 | /*! 18 | * The 'DBMessagingCollectionViewSlidingTimestampFlowLayout' extends a 'DBMessagingCollectionViewBaseFlowLayout' to provide 19 | * support for sliding timestamps that are displayed by setting the 'tappedIndexPath'. 20 | * 21 | * @discussion The 'DBMessagingCollectionViewSlidingTimestampFlowLayout' displays timestamps on the right-hand side of a 22 | * message by pulling in the horizontally similar to iMessage. 23 | * 24 | * @see DBMessagingCollectionViewBaseFlowLayout 25 | */ 26 | @interface DBMessagingCollectionViewSlidingTimestampFlowLayout : DBMessagingCollectionViewBaseFlowLayout 27 | 28 | @property (strong, nonatomic) UIPanGestureRecognizer *panGesture; 29 | 30 | @property (assign, nonatomic) BOOL panning; 31 | @property (assign, nonatomic) CGPoint panLocation; 32 | @property (assign, nonatomic) CGPoint startLocation; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /MessagingKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSLocationWhenInUseUsageDescription 6 | Request when in usage permission. 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | com.self.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | ${CURRENT_PROJECT_VERSION} 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Source/Components/Photos/DBMessagingPhotoPickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingPhotoPickerController.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-10. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | typedef NS_ENUM(NSInteger, DBMessagingPhotoPickerControllerOption) { 18 | DBMessagingPhotoPickerControllerOptionPhotoLibrary, 19 | DBMessagingPhotoPickerControllerOptionTakePhoto, 20 | DBMessagingPhotoPickerControllerOptionCancel 21 | }; 22 | 23 | typedef NS_ENUM(NSInteger, DBMessagingPhotoPickerControllerAction) { 24 | DBMessagingPhotoPickerControllerActionSend, 25 | DBMessagingPhotoPickerControllerActionComment 26 | }; 27 | 28 | @class DBMessagingPhotoPickerController; 29 | 30 | @protocol DBMessagingPhotoPickerControllerDelegate 31 | 32 | @optional 33 | 34 | - (void)photoPickerController:(DBMessagingPhotoPickerController *)picker didFinishPickingPhotos:(NSArray *)photos action:(DBMessagingPhotoPickerControllerAction)action; 35 | 36 | - (void)photoPickerController:(DBMessagingPhotoPickerController *)picker didDismissWithOption:(DBMessagingPhotoPickerControllerOption)option; 37 | 38 | @end 39 | 40 | NS_CLASS_AVAILABLE_IOS(8_0) @interface DBMessagingPhotoPickerController : UIViewController 41 | 42 | @property (weak, nonatomic) id delegate; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewFlowLayoutInvalidationContext.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewFlowLayoutInvalidationContext.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-23. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingCollectionViewFlowLayoutInvalidationContext.h" 16 | 17 | @implementation DBMessagingCollectionViewFlowLayoutInvalidationContext 18 | 19 | - (instancetype)init 20 | { 21 | self = [super init]; 22 | if (self) { 23 | self.invalidateFlowLayoutDelegateMetrics = NO; 24 | self.invalidateFlowLayoutAttributes = NO; 25 | _emptyCache = NO; 26 | } 27 | return self; 28 | } 29 | 30 | + (instancetype)context 31 | { 32 | DBMessagingCollectionViewFlowLayoutInvalidationContext *context = [[DBMessagingCollectionViewFlowLayoutInvalidationContext alloc] init]; 33 | context.invalidateFlowLayoutDelegateMetrics = YES; 34 | context.invalidateFlowLayoutAttributes = YES; 35 | return context; 36 | } 37 | 38 | #pragma mark - NSObject 39 | 40 | - (NSString *)description 41 | { 42 | return [NSString stringWithFormat:@"<%@: invalidateFlowLayoutDelegateMetrics=%d, invalidateFlowLayoutAttributes=%d, invalidateDataSourceCounts=%d>", 43 | [self class], 44 | self.invalidateFlowLayoutDelegateMetrics, 45 | self.invalidateFlowLayoutAttributes, 46 | self.invalidateDataSourceCounts]; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Source/Categories/UIColor+Messaging.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Messaging.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-18. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface UIColor (Messaging) 18 | 19 | /** 20 | * @return A color object containing RBG values similar to the iOS 7 messages app gray bubble color. 21 | */ 22 | + (UIColor *)iMessageGrayColor; 23 | 24 | /** 25 | * @return A color object containing RBG values similar to the iOS 7 messages app blue bubble color. 26 | */ 27 | + (UIColor *)iMessageBlueColor; 28 | 29 | /** 30 | * @return A color object containing RBG values similar to the iOS 7 messages app green bubble color. 31 | */ 32 | + (UIColor *)iMessageGreenColor; 33 | 34 | /** 35 | * @return A color object containing RBG values similar to the tint color of the iOS 7 messages app icons. 36 | */ 37 | + (UIColor *)defaultToolbarTintColor; 38 | 39 | /** 40 | * Creates and returns a new color object whose brightness component is decreased by the given value, using the initial color 41 | * values of the receiver. 42 | * 43 | * @param value A floating point value describing the amount by which to decrease the brightness of the receiver. 44 | * 45 | * @return A new color object whose brightness is decreased by the given values. The other color values remain the same as the 46 | * receiver. 47 | */ 48 | - (UIColor *)colorByDarkeningColorWithValue:(CGFloat)value; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingImageMediaCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingImageMediaCell.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-09. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingImageMediaCell.h" 16 | 17 | static NSString *kDBMessagingImageMediaCellMimeType = @"image/jpeg"; 18 | 19 | @interface DBMessagingImageMediaCell () 20 | 21 | @property (strong, nonatomic) UIImageView *imageView; 22 | 23 | @end 24 | 25 | @implementation DBMessagingImageMediaCell 26 | 27 | + (NSString *)mimeType { 28 | return kDBMessagingImageMediaCellMimeType; 29 | } 30 | 31 | + (void)setMimeType:(NSString *)mimeType { 32 | NSAssert(![mimeType isEqualToString:@""] || mimeType != nil, @"Mime type for class %@ cannot be nil.", [self class]); 33 | kDBMessagingImageMediaCellMimeType = mimeType; 34 | } 35 | 36 | + (NSString *)cellReuseIdentifier { 37 | return NSStringFromClass([self class]); 38 | } 39 | 40 | - (instancetype)initWithFrame:(CGRect)frame 41 | { 42 | self = [super initWithFrame:frame]; 43 | if (self) { 44 | _imageView = [[UIImageView alloc] init]; 45 | [_imageView setContentMode:UIViewContentModeScaleAspectFill]; 46 | [_imageView setUserInteractionEnabled:YES]; 47 | [_imageView setFrame:self.messageBubbleImageView.frame]; 48 | [_imageView setBackgroundColor:[UIColor clearColor]]; 49 | 50 | self.mediaView = _imageView; 51 | } 52 | return self; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewFlowLayoutInvalidationContext.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewFlowLayoutInvalidationContext.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-23. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | /** 18 | * A 'DBMessagingCollectionViewFlowLayoutInvalidationContext' instance specifies properties for 19 | * determining whether to recompute the size of items or their position in the layout. 20 | * The flow layout object creates instances of this class when it needs to invalidate its contents 21 | * in response to changes. 22 | */ 23 | @interface DBMessagingCollectionViewFlowLayoutInvalidationContext : UICollectionViewFlowLayoutInvalidationContext 24 | 25 | /** 26 | * A boolean indication whether to empty the layout information cache for items and views in the layout 27 | * The default is 'NO'. 28 | */ 29 | @property (assign, nonatomic) BOOL emptyCache; 30 | 31 | /** 32 | * Creates and returns a new 'DBMessagingCollectionViewFlowLayoutInvalidationContext' object. 33 | * 34 | * @discussion When you need to invalidate the 'DBMessagignCollectionView' object for your 35 | * 'DBMessagingViewController' subclass, you should use this method to instantiate a new invalidation 36 | * context and pass this object to 'invalidateLayoutWithContext:'. 37 | * 38 | * @return An initialized invalidation context object if successful, otherwise 'nil'. 39 | */ 40 | + (instancetype)context; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingInputTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingInputTextView.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-18. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | #import 18 | 19 | @protocol DBMessagingInputTextViewDelegate 20 | 21 | @optional 22 | - (void)textViewDidChangeFrame:(UITextView *)textView delta:(CGFloat)delta; 23 | 24 | @end 25 | 26 | @interface DBMessagingInputTextView : UITextView 27 | 28 | @property (weak, nonatomic) id delegate; 29 | 30 | @property (assign, nonatomic) CGFloat maximumHeight; 31 | @property (assign, nonatomic) NSInteger borderWidth UI_APPEARANCE_SELECTOR; 32 | @property (assign, nonatomic) NSInteger cornerRadius UI_APPEARANCE_SELECTOR; 33 | @property (strong, nonatomic) UIColor *borderColor UI_APPEARANCE_SELECTOR; 34 | @property (strong, nonatomic) NSString *placeholderText UI_APPEARANCE_SELECTOR; 35 | @property (strong, nonatomic) UIColor *placeholderColor UI_APPEARANCE_SELECTOR; 36 | 37 | @property (strong, nonatomic, readonly) NSMutableArray *attatchmentRanges; 38 | @property (strong, nonatomic, readonly) NSArray *messageParts; 39 | 40 | // Attatchments 41 | - (void)addImageAttatchment:(UIImage *)image; 42 | - (void)addLocationAttatchment:(CLLocation *)location; 43 | - (void)removeAttatchmentAtRange:(NSRange)range; 44 | 45 | - (NSString *)currentlyComposedTextMessagePart; 46 | - (void)clear; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewHiddenTimestampFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewHiddenTimestampFlowLayout.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-08. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingCollectionViewBaseFlowLayout.h" 16 | 17 | @class DBMessagingCollectionView; 18 | 19 | /*! 20 | * The 'DBMessagingCollectionViewHiddenTimestampFlowLayout' extends a 'DBMessagingCollectionViewBaseFlowLayout' to provide 21 | * support for hidden timestamps that are displayed by setting the 'tappedIndexPath'. 22 | * 23 | * @discussion The 'DBMessagingCollectionViewHiddenTimestampFlowLayout' displays timestamps below a message when tapped similar 24 | * to Facebook Messenger. 25 | * 26 | * @see DBMessagingCollectionViewBaseFlowLayout 27 | */ 28 | @interface DBMessagingCollectionViewHiddenTimestampFlowLayout : DBMessagingCollectionViewBaseFlowLayout 29 | 30 | /** 31 | * Specifies the indexPath that recieved a tap event in order to display or hide a 'MessagingTimestampSupplementaryView'. 32 | */ 33 | @property (strong, nonatomic) NSIndexPath *tappedIndexPath; 34 | 35 | /** 36 | * Specifies the padding that should be applied to the 'MessagingTimestampSupplementaryView'. 37 | * 38 | * @discussion The 'timestampSupplementaryView' height is calculated using the boundingBox of the attributed string 39 | * passed by the appropriate dataSource method. 40 | */ 41 | @property (assign, nonatomic) CGFloat timestampSupplementaryViewPadding; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingLoadEarlierMessagesHeaderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingLoadEarlierMessagesHeaderView.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-26. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingLoadEarlierMessagesHeaderView.h" 16 | 17 | @interface DBMessagingLoadEarlierMessagesHeaderView () 18 | 19 | @property (nonatomic) UILabel *loadMoreLabel; 20 | @property (nonatomic) UIActivityIndicatorView *activityIndicator; 21 | 22 | @end 23 | 24 | @implementation DBMessagingLoadEarlierMessagesHeaderView 25 | 26 | + (NSString *)viewReuseIdentifier { 27 | return NSStringFromClass([self class]); 28 | } 29 | 30 | + (CGFloat)heightForHeader 31 | { 32 | return 30.0f; 33 | } 34 | 35 | - (instancetype)initWithFrame:(CGRect)frame 36 | { 37 | self = [super initWithFrame:frame]; 38 | if (self) { 39 | 40 | [self setBackgroundColor:[UIColor lightGrayColor]]; 41 | 42 | _loadMoreLabel = [[UILabel alloc] init]; 43 | [_loadMoreLabel setTextAlignment:NSTextAlignmentCenter]; 44 | [_loadMoreLabel setNumberOfLines:1]; 45 | [_loadMoreLabel setText:@"Load older messages"]; 46 | [_loadMoreLabel setTextColor:[UIColor whiteColor]]; 47 | [self addSubview:_loadMoreLabel]; 48 | } 49 | return self; 50 | } 51 | 52 | - (void)startAnimating 53 | { 54 | [self.activityIndicator startAnimating]; 55 | } 56 | 57 | - (void)stopAnimating 58 | { 59 | [self.activityIndicator stopAnimating]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingInputToolbar.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessageInputView.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-19. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @class DBMessagingInputToolbar; 18 | @class DBMessagingInputTextView; 19 | 20 | typedef NS_ENUM(NSInteger, DBMessagingInputToolbarItemPosition) { 21 | DBMessagingInputToolbarItemPositionLeft, 22 | DBMessagingInputToolbarItemPositionRight 23 | // DBMessagingInputToolbarItemPositionBottom - In development 24 | // DBMessagingInputToolbarItemPositionTop - In development 25 | }; 26 | 27 | @protocol DBMessagingInputToolbarDelegate 28 | 29 | - (void)messagingInputToolbarDidBeginEditing:(DBMessagingInputToolbar *)toolbar; 30 | - (void)messagingInputToolbar:(DBMessagingInputToolbar *)toolbar shouldChangeFrame:(CGFloat)change; 31 | 32 | @end 33 | 34 | @interface DBMessagingInputToolbar : UIView 35 | 36 | @property (weak, nonatomic) id delegate; 37 | 38 | @property (strong, nonatomic) UIColor *borderColor; 39 | @property (assign, nonatomic) NSInteger borderWidth; 40 | @property (assign, nonatomic) BOOL blur; 41 | 42 | @property (strong, nonatomic) UIBarButtonItem *sendBarButtonItem; 43 | @property (strong, nonatomic, readonly) UIToolbar *contentToolbar; 44 | @property (strong, nonatomic, readonly) DBMessagingInputTextView *textView; 45 | 46 | - (void)addItem:(UIBarButtonItem *)item position:(DBMessagingInputToolbarItemPosition)position animated:(BOOL)animated; 47 | - (void)toggleSendButtonEnabled; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingCollectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionView.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-21. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | #import "DBMessagingTextCell.h" 18 | #import "DBMessagingMediaCell.h" 19 | #import "DBMessagingCollectionViewBaseFlowLayout.h" 20 | 21 | #import "DBMessagingCollectionViewDelegate.h" 22 | #import "DBMessagingCollectionViewDataSource.h" 23 | #import "DBMessagingCollectionViewDelegateFlowLayout.h" 24 | 25 | @class DBMessagingLoadEarlierMessagesHeaderView; 26 | @class DBMessagingTypingIndicatorFooterView; 27 | @class DBMessagingTimestampSupplementaryView; 28 | 29 | @interface DBMessagingCollectionView : UICollectionView 30 | 31 | @property (weak, nonatomic) id dataSource; 32 | @property (weak, nonatomic) id delegate; 33 | @property (strong, nonatomic) DBMessagingCollectionViewBaseFlowLayout *collectionViewLayout; 34 | 35 | @property (weak, nonatomic) DBMessagingLoadEarlierMessagesHeaderView *loadMoreHeaderView; 36 | @property (weak, nonatomic) DBMessagingTypingIndicatorFooterView *typingIndicatorFooterView; 37 | 38 | - (DBMessagingLoadEarlierMessagesHeaderView *)dequeueLoadEarlierMessagesHeaderViewForIndexPath:(NSIndexPath *)indexPath; 39 | - (DBMessagingTypingIndicatorFooterView *)dequeueTypingIndicatorFooterViewForIndexPath:(NSIndexPath *)indexPath; 40 | - (DBMessagingTimestampSupplementaryView *)dequeueTimestampSupplementaryViewForIndexPath:(NSIndexPath *)indexPath; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Source/Factories/DBMessageBubbleFactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessageBubbleFactory.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-01. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | /** 18 | * A 'DBMessageBubbleFactory' contains class methods for creating configured 'message bubbles'. 19 | */ 20 | @interface DBMessageBubbleFactory : NSObject 21 | 22 | /** 23 | * Creates an returns an image view object with the specified color for outgoing messages. 24 | * The 'image' property of the image view is configured with a flat bubble image, masked to the given color. 25 | * The 'highlightedImage' property is configured similarly, but with a darkened version of the given color. 26 | * 27 | * @param color The color of the bubble image in the image view. This value must not be 'nil'. 28 | * 29 | * @return An initialized image view object if created successfully, 'nil' otherwise. 30 | */ 31 | + (UIImageView *)outgoingMessageBubbleImageWithColor:(UIColor *)color template:(UIImage *)bubbleTemplate; 32 | 33 | /** 34 | * Creates an returns an image view object with the specified color for incoming messages. 35 | * The 'image' property of the image view is configured with a flat bubble image, masked to the given color. 36 | * The 'highlightedImage' property is configured similarly, but with a darkened version of the given color. 37 | * 38 | * @param color The color of the bubble image in the image view. This value must not be 'nil'. 39 | * 40 | * @return An initialized image view object if created successfully, 'nil' otherwise. 41 | */ 42 | + (UIImageView *)incomingMessageBubbleImageWithColor:(UIColor *)color template:(UIImage *)bubbleTemplate; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Source/Categories/UIImage+AnimatedGIF.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+AnimatedGIF.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/mayoff/uiimage-from-animated-gif 7 | // 8 | // 9 | // Created by Rob Mayoff on 2012-01-27. 10 | // Copyright (c) 2014 Rob Mayoff. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @interface UIImage (AnimatedGIF) 18 | 19 | /** 20 | * The data is interpreted as a GIF to create an animated 'UIImage' using the source images in the GIF. The GIF stores a separate 21 | * duration for each frame, in units of centiseconds (hundredths of a second). However, a 'UIImage' only has a single, total 22 | * 'duration' property, which is a floating-point number. To handle this mismatch, each source image (from the GIF) is added to 23 | * 'animation' a varying number of times to match the ratios between the frame durations in the GIF. 24 | * 25 | * For example, suppose the GIF contains three frames. Frame 0 has duration 3. Frame 1 has duration 9. Frame 2 has duration 15. 26 | * Each duration is divided by the greatest common denominator of all the durations, which is 3, and add each frame the resulting 27 | * number of times. Thus 'animation' will contain frame 0 3/3 = 1 time, then frame 1 9/3 = 3 times, then frame 2 15/3 = 5 times. 28 | * The 'animation.duration' is set to (3+9+15)/100 = 0.27 seconds. 29 | * 30 | * @param data The data embedded in the GIF. 31 | * 32 | * @return An animated UIImage that displays a GIF. 33 | */ 34 | + (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)data; 35 | 36 | /** 37 | * The contents of the URL are interpreted as a GIF to create an animated 'UIImage' using the source images in the GIF. 38 | * 39 | * @param url The URL that contains the GIF. 40 | * 41 | * @return An animated UIImage that displays a GIF. 42 | */ 43 | + (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingParentCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingParentCell.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-17. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @class DBMessagingParentCell; 18 | 19 | typedef NS_ENUM(NSInteger, MessageBubbleType) { 20 | MessageBubbleTypeOutgoing, 21 | MessageBubbleTypeIncoming 22 | }; 23 | 24 | @protocol DBMessagingParentCellDelegate 25 | 26 | @optional 27 | - (void)messageCell:(DBMessagingParentCell *)cell didTapAvatarImageView:(UIImageView *)avatarImageView; 28 | 29 | @end 30 | 31 | @interface DBMessagingParentCell : UICollectionViewCell 32 | 33 | + (NSString *)mimeType; 34 | 35 | + (void)setMimeType:(NSString *)mimeType; 36 | 37 | + (NSString *)cellReuseIdentifier; 38 | 39 | @property (weak, nonatomic) id delegate; 40 | @property (weak, nonatomic) UICollectionView *collectionView; 41 | 42 | @property (strong, nonatomic) UILabel *messageTopLabel; 43 | @property (strong, nonatomic) UILabel *cellTopLabel; 44 | @property (strong, nonatomic) UILabel *cellBottomLabel; 45 | @property (strong, nonatomic) UIImageView *avatarImageView; 46 | @property (strong, nonatomic) UIImageView *accessoryImageView; 47 | @property (strong, nonatomic) UIImageView *messageBubbleImageView; 48 | @property (assign, nonatomic) MessageBubbleType type; 49 | @property (assign, nonatomic) BOOL hideAvatar; 50 | 51 | @property (assign, nonatomic) CGSize incomingAvatarSize; 52 | @property (assign, nonatomic) CGSize outgoingAvatarSize; 53 | @property (assign, nonatomic) CGFloat messageTopLabelHeight; 54 | @property (assign, nonatomic) CGFloat cellTopLabelHeight; 55 | @property (assign, nonatomic) CGFloat cellBottomLabelHeight; 56 | @property (assign, nonatomic) CGFloat messageBubbleLeftRightMargin; 57 | @property (assign, nonatomic) CGFloat incomingMessageBubbleAvatarSpacing; 58 | @property (assign, nonatomic) CGFloat outgoingMessageBubbleAvatarSpacing; 59 | @property (assign, nonatomic) UIEdgeInsets messageBubbleTextContainerInsets; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Source/DBMessagingKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingKit.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-12. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | //! Project version number for DBMessagingKit. 16 | FOUNDATION_EXPORT double DBMessagingKitVersionNumber; 17 | 18 | //! Project version string for DBMessagingKit. 19 | FOUNDATION_EXPORT const unsigned char DBMessagingKitVersionString[]; 20 | 21 | #ifndef DBMessagingKit_DBMessagingKit_h 22 | #define DBMessagingKit_DBMessagingKit_h 23 | 24 | #import "DBMessagingKitConstants.h" 25 | 26 | // Protocols 27 | #import "DBMessagingCollectionViewDataSource.h" 28 | #import "DBMessagingCollectionViewDelegate.h" 29 | #import "DBMessagingCollectionViewDelegateFlowLayout.h" 30 | 31 | // Factories 32 | #import "DBMessageBubbleFactory.h" 33 | #import "DBMessagingTimestampFormatter.h" 34 | 35 | // Controllers 36 | #import "DBMessagingViewController.h" 37 | #import "DBMessageBubbleController.h" 38 | #import "DBInteractiveKeyboardController.h" 39 | #import "DBSystemSoundPlayer.h" 40 | 41 | // Layout 42 | #import "DBMessagingCollectionViewBaseFlowLayout.h" 43 | #import "DBMessagingCollectionViewHiddenTimestampFlowLayout.h" 44 | #import "DBMessagingCollectionViewSlidingTimestampFlowLayout.h" 45 | #import "DBMessagingCollectionViewFlowLayoutInvalidationContext.h" 46 | #import "DBMessagingCollectionViewLayoutAttributes.h" 47 | 48 | // Views 49 | #import "DBMessagingInputToolbar.h" 50 | #import "DBMessagingCollectionView.h" 51 | #import "DBMessagingTextCell.h" 52 | #import "DBMessagingMediaCell.h" 53 | #import "DBMessagingImageMediaCell.h" 54 | #import "DBMessagingVideoMediaCell.h" 55 | #import "DBMessagingLocationMediaCell.h" 56 | #import "DBMessagingInputTextView.h" 57 | #import "DBMessagingCellTextView.h" 58 | #import "DBMessagingTimestampSupplementaryView.h" 59 | #import "DBMessagingLoadEarlierMessagesHeaderView.h" 60 | #import "DBMessagingTypingIndicatorFooterView.h" 61 | 62 | // Categories 63 | #import "NSAttributedString+Messaging.h" 64 | #import "NSMutableAttributedString+Messaging.h" 65 | #import "UIColor+Messaging.h" 66 | #import "UIImage+Messaging.h" 67 | #import "UIImage+AnimatedGIF.h" 68 | 69 | // Components 70 | #import "DBMessagingPhotoPickerController.h" 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /Source/Protocols/DBMessagingCollectionViewDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewDelegate.h 3 | // DBMessagingKit 4 | // 5 | // Created by Devon Boyer on 2015-02-09. 6 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DBMessagingMediaView; 12 | 13 | /** 14 | * The 'DBMessagingCollectionViewDelegateFlowLayout' protocol defines methods that allow you to respond to additional 15 | * actions on its items. 16 | */ 17 | @protocol DBMessagingCollectionViewDelegate 18 | 19 | @optional 20 | 21 | /** 22 | * Notifies the delegate that the avatar image view at the specified indexPath did receive a tap event. 23 | * 24 | * @param collectionView The collection view object that is notifying the delegate of the tap event. 25 | * @param avatarImageView The avatar image view that was tapped. 26 | * @param indexPath The index path of the item for which the avatar was tapped. 27 | */ 28 | - (void)collectionView:(UICollectionView *)collectionView didTapAvatarImageView:(UIImageView *)avatarImageView atIndexPath:(NSIndexPath *)indexPath; 29 | 30 | /** 31 | * Notifies the delegate that the image view at the specified indexPath did receive a tap event. 32 | * 33 | * @param collectionView The collection view object that is notifying the delegate of the tap event. 34 | * @param imageView The image view that was tapped. 35 | * @param indexPath The index path of the item for which the photo was tapped. 36 | */ 37 | - (void)collectionView:(UICollectionView *)collectionView didTapMediaView:(DBMessagingMediaView *)mediaView atIndexPath:(NSIndexPath *)indexPath; 38 | 39 | /** 40 | * Notifies the delegate that the message bubble at the specified indexPath did receive a tap event. 41 | * 42 | * @param collectionView The collection view object that is notifying the delegate of the tap event. 43 | * @param messageBubbleImageView The message bubble image view that was tapped. 44 | * @param indexPath The index path of the item for which the message bubble was tapped. 45 | * 46 | * @discussion A tap event for a message bubble will either mean that a timestamp is being displayed, or just ended 47 | * being displayed. 48 | */ 49 | - (void)collectionView:(UICollectionView *)collectionView didTapMessageBubbleImageView:(UIImageView *)messageBubbleImageView atIndexPath:(NSIndexPath *)indexPath; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /MessagingKit/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-12-04. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "AppDelegate.h" 16 | #import "ViewController.h" 17 | 18 | @interface AppDelegate () 19 | 20 | @end 21 | 22 | @implementation AppDelegate 23 | 24 | 25 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 26 | // Override point for customization after application launch. 27 | 28 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 29 | self.window.backgroundColor = [UIColor whiteColor]; 30 | [self.window makeKeyAndVisible]; 31 | 32 | self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; 33 | 34 | return YES; 35 | } 36 | 37 | - (void)applicationWillResignActive:(UIApplication *)application { 38 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 39 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 40 | } 41 | 42 | - (void)applicationDidEnterBackground:(UIApplication *)application { 43 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 44 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 45 | } 46 | 47 | - (void)applicationWillEnterForeground:(UIApplication *)application { 48 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 49 | } 50 | 51 | - (void)applicationDidBecomeActive:(UIApplication *)application { 52 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 53 | } 54 | 55 | - (void)applicationWillTerminate:(UIApplication *)application { 56 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingLocationMediaCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingLocationMediaCell.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-09. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingLocationMediaCell.h" 16 | 17 | #import "DBMessagingLocationPin.h" 18 | 19 | #define METERS_PER_MILE 1609.344 20 | 21 | static NSString *kDBMessagingLocationMediaCellMimeType = @"geo"; 22 | 23 | 24 | @interface DBMessagingLocationMediaCell () 25 | 26 | @property (strong, nonatomic) MKMapView *mapView; 27 | 28 | @end 29 | 30 | @implementation DBMessagingLocationMediaCell 31 | 32 | + (NSString *)mimeType { 33 | return kDBMessagingLocationMediaCellMimeType; 34 | } 35 | 36 | + (void)setMimeType:(NSString *)mimeType { 37 | NSAssert(![mimeType isEqualToString:@""] || mimeType != nil, @"Mime type for class %@ cannot be nil.", [self class]); 38 | kDBMessagingLocationMediaCellMimeType = mimeType; 39 | } 40 | 41 | + (NSString *)cellReuseIdentifier { 42 | return NSStringFromClass([self class]); 43 | } 44 | 45 | - (instancetype)initWithFrame:(CGRect)frame 46 | { 47 | self = [super initWithFrame:frame]; 48 | if (self) { 49 | _mapView = [[MKMapView alloc] init]; 50 | [_mapView setZoomEnabled:YES]; 51 | [_mapView setScrollEnabled:YES]; 52 | [_mapView setShowsUserLocation:YES]; 53 | 54 | self.mediaView = _mapView; 55 | } 56 | return self; 57 | } 58 | 59 | #pragma mark - Setters 60 | 61 | - (void)setCoordinate:(CLLocationCoordinate2D)coordinate { 62 | _coordinate = coordinate; 63 | 64 | MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(coordinate, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE); 65 | 66 | [_mapView setRegion:viewRegion animated:YES]; 67 | 68 | // Create an annotation 69 | DBMessagingLocationPin *annotation = [[DBMessagingLocationPin alloc] initWithCoordinate:coordinate]; 70 | [_mapView addAnnotation:annotation]; 71 | } 72 | 73 | #pragma mark - MKMapViewDelegate 74 | 75 | - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation { 76 | 77 | if ([annotation isKindOfClass:[DBMessagingLocationPin class]]) { 78 | 79 | MKAnnotationView *annotationView = [_mapView dequeueReusableAnnotationViewWithIdentifier:[DBMessagingLocationPin annotationReuseIdentifier]]; 80 | annotationView.enabled = YES; 81 | annotationView.canShowCallout = YES; 82 | annotationView.annotation = annotation; 83 | return annotationView; 84 | } 85 | 86 | return nil; 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /Source/Categories/UIColor+Messaging.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Messaging.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-18. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "UIColor+Messaging.h" 16 | 17 | @implementation UIColor (Messaging) 18 | 19 | #pragma mark - iMessage 20 | 21 | + (UIColor *)iMessageGrayColor { 22 | return [UIColor colorWithRed:240 / 255.0 23 | green:240 / 255.0 24 | blue:240 / 255.0 25 | alpha:1.0]; 26 | } 27 | 28 | + (UIColor *)iMessageBlueColor 29 | { 30 | return [UIColor colorWithRed:0 / 255.0 31 | green:122 / 255.0 32 | blue:255 / 255.0 33 | alpha:1.0]; 34 | } 35 | 36 | + (UIColor *)iMessageGreenColor 37 | { 38 | return [UIColor colorWithRed:76 / 255.0 39 | green:215 / 255.0 40 | blue:100 / 255.0 41 | alpha:1.0]; 42 | } 43 | 44 | + (UIColor *)defaultToolbarTintColor { 45 | return [UIColor colorWithRed:133 / 255.0 46 | green:141 / 255.0 47 | blue:153 / 255.0 48 | alpha:1.0]; 49 | } 50 | 51 | - (UIColor *)colorByDarkeningColorWithValue:(CGFloat)value 52 | { 53 | NSUInteger totalComponents = CGColorGetNumberOfComponents(self.CGColor); 54 | BOOL isGreyscale = (totalComponents == 2) ? YES : NO; 55 | 56 | CGFloat *oldComponents = (CGFloat *)CGColorGetComponents(self.CGColor); 57 | CGFloat newComponents[4]; 58 | 59 | if (isGreyscale) { 60 | newComponents[0] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; 61 | newComponents[1] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; 62 | newComponents[2] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; 63 | newComponents[3] = oldComponents[1]; 64 | } 65 | else { 66 | newComponents[0] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; 67 | newComponents[1] = oldComponents[1] - value < 0.0f ? 0.0f : oldComponents[1] - value; 68 | newComponents[2] = oldComponents[2] - value < 0.0f ? 0.0f : oldComponents[2] - value; 69 | newComponents[3] = oldComponents[3]; 70 | } 71 | 72 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 73 | CGColorRef newColor = CGColorCreate(colorSpace, newComponents); 74 | CGColorSpaceRelease(colorSpace); 75 | 76 | UIColor *retColor = [UIColor colorWithCGColor:newColor]; 77 | CGColorRelease(newColor); 78 | 79 | return retColor; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingCellTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCellTextView.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-11-30. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingCellTextView.h" 16 | 17 | @implementation DBMessagingCellTextView 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame { 20 | self = [super initWithFrame:frame]; 21 | if (self) { 22 | [self setup]; 23 | } 24 | return self; 25 | } 26 | 27 | - (instancetype)init { 28 | self = [super init]; 29 | if (self) { 30 | [self setup]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)awakeFromNib { 36 | [super awakeFromNib]; 37 | 38 | [self setup]; 39 | } 40 | 41 | - (void)setup { 42 | self.textColor = [UIColor whiteColor]; 43 | self.editable = NO; 44 | self.selectable = YES; 45 | self.userInteractionEnabled = YES; 46 | self.dataDetectorTypes = UIDataDetectorTypeNone; 47 | self.showsHorizontalScrollIndicator = NO; 48 | self.showsVerticalScrollIndicator = NO; 49 | self.scrollEnabled = NO; 50 | self.backgroundColor = [UIColor clearColor]; 51 | self.contentInset = UIEdgeInsetsZero; 52 | self.scrollIndicatorInsets = UIEdgeInsetsZero; 53 | self.contentOffset = CGPointZero; 54 | self.textContainerInset = UIEdgeInsetsZero; 55 | self.textContainer.lineFragmentPadding = 0.0; 56 | [self setLinkTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor], 57 | NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid)}]; 58 | } 59 | 60 | - (void)setSelectedRange:(NSRange)selectedRange { 61 | // Prevent selecting text 62 | [super setSelectedRange:NSMakeRange(NSNotFound, 0)]; 63 | } 64 | 65 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { 66 | 67 | // Ingore double tap to prevent copy/define/et.c menu from showing 68 | if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { 69 | UITapGestureRecognizer *tap = (UITapGestureRecognizer *)gestureRecognizer; 70 | if (tap.numberOfTapsRequired == 2) { 71 | return NO; 72 | } 73 | } 74 | 75 | return YES; 76 | } 77 | 78 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { 79 | 80 | // Ingore double tap to prevent copy/define/et.c menu from showing 81 | if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { 82 | UITapGestureRecognizer *tap = (UITapGestureRecognizer *)gestureRecognizer; 83 | if (tap.numberOfTapsRequired == 2) { 84 | return NO; 85 | } 86 | } 87 | 88 | return YES; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /Source/Factories/DBMessageBubbleFactory.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessageBubbleFactory.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-01. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessageBubbleFactory.h" 16 | #import "UIImage+Messaging.h" 17 | #import "UIColor+Messaging.h" 18 | 19 | @implementation DBMessageBubbleFactory 20 | 21 | #pragma mark - Public 22 | 23 | + (UIImageView *)outgoingMessageBubbleImageWithColor:(UIColor *)color template:(UIImage *)bubbleTemplate{ 24 | NSParameterAssert(bubbleTemplate != nil); 25 | return [DBMessageBubbleFactory bubbleImageWithColor:color flippedForIncoming:NO template:bubbleTemplate]; 26 | } 27 | 28 | + (UIImageView *)incomingMessageBubbleImageWithColor:(UIColor *)color template:(UIImage *)bubbleTemplate { 29 | NSParameterAssert(bubbleTemplate != nil); 30 | return [DBMessageBubbleFactory bubbleImageWithColor:color flippedForIncoming:YES template:bubbleTemplate]; 31 | } 32 | 33 | #pragma mark - Private 34 | 35 | + (UIImageView *)bubbleImageWithColor:(UIColor *)color flippedForIncoming:(BOOL)flippedForIncoming template:(UIImage *)bubbleTemplate { 36 | 37 | UIImage *bubble = bubbleTemplate; 38 | 39 | UIImage *normalBubble = [bubble imageOverlayedWithColor:color]; 40 | UIImage *highlightedBubble = [bubble imageOverlayedWithColor:[color colorByDarkeningColorWithValue:0.08f]]; 41 | 42 | if (flippedForIncoming) { 43 | normalBubble = [DBMessageBubbleFactory horizontallyFlippedImageFromImage:normalBubble]; 44 | highlightedBubble = [DBMessageBubbleFactory horizontallyFlippedImageFromImage:highlightedBubble]; 45 | } 46 | 47 | // Make image stretchable from center point 48 | CGPoint center = CGPointMake(bubble.size.width / 2.0f, bubble.size.height / 2.0f); 49 | UIEdgeInsets capInsets = UIEdgeInsetsMake(center.y, center.x, center.y, center.x); 50 | 51 | normalBubble = [DBMessageBubbleFactory stretchableImageFromImage:normalBubble withCapInsets:capInsets]; 52 | highlightedBubble = [DBMessageBubbleFactory stretchableImageFromImage:highlightedBubble withCapInsets:capInsets]; 53 | 54 | UIImageView *imageView = [[UIImageView alloc] initWithImage:normalBubble highlightedImage:highlightedBubble]; 55 | imageView.backgroundColor = [UIColor whiteColor]; 56 | return imageView; 57 | } 58 | 59 | + (UIImage *)horizontallyFlippedImageFromImage:(UIImage *)image { 60 | return [UIImage imageWithCGImage:image.CGImage 61 | scale:image.scale 62 | orientation:UIImageOrientationUpMirrored]; 63 | } 64 | 65 | + (UIImage *)stretchableImageFromImage:(UIImage *)image withCapInsets:(UIEdgeInsets)capInsets { 66 | return [image resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /Source/Categories/UIImage+Messaging.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Messaging.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-18. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "UIImage+Messaging.h" 16 | 17 | #import 18 | #import 19 | 20 | @implementation UIImage (Messaging) 21 | 22 | #pragma mark - Base64 23 | 24 | - (NSString *)encodeToBase64String { 25 | return [UIImageJPEGRepresentation(self, 0.5) base64EncodedStringWithOptions:0]; 26 | } 27 | 28 | + (UIImage *)decodeBase64StringToImage:(NSString *)encodedString { 29 | NSData *data = [[NSData alloc] initWithBase64EncodedString:encodedString options:NSDataBase64DecodingIgnoreUnknownCharacters]; 30 | return [[UIImage alloc] initWithData:data]; 31 | } 32 | 33 | #pragma mark - Image Manipulation 34 | 35 | + (UIImage *)imageByRoundingCorners:(CGFloat)cornerRadius ofImage:(UIImage *)source { 36 | 37 | CGSize imageSize = source.size; 38 | CGRect drawingRect = CGRectMake(0, 0, imageSize.width, imageSize.height); 39 | 40 | // Begin a new image that will be the new image with the rounded corners 41 | UIGraphicsBeginImageContextWithOptions(imageSize, NO, [UIScreen mainScreen].scale); 42 | 43 | // Add a clip before drawing anything, in the shape of an rounded rect 44 | [[UIBezierPath bezierPathWithRoundedRect:drawingRect 45 | cornerRadius:cornerRadius] addClip]; 46 | // Draw the image 47 | [source drawInRect:drawingRect]; 48 | 49 | UIImage *finishedImage = UIGraphicsGetImageFromCurrentImageContext(); 50 | 51 | UIGraphicsEndImageContext(); 52 | 53 | return finishedImage; 54 | } 55 | 56 | + (UIImage *)imageWithColor:(UIColor *)color 57 | { 58 | CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); 59 | UIGraphicsBeginImageContext(rect.size); 60 | CGContextRef context = UIGraphicsGetCurrentContext(); 61 | 62 | CGContextSetFillColorWithColor(context, [color CGColor]); 63 | CGContextFillRect(context, rect); 64 | 65 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 66 | UIGraphicsEndImageContext(); 67 | 68 | return image; 69 | } 70 | 71 | - (UIImage *)imageOverlayedWithColor:(UIColor *)color 72 | { 73 | CGRect imageRect = CGRectMake(0.0f, 0.0f, self.size.width, self.size.height); 74 | 75 | UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, self.scale); 76 | CGContextRef context = UIGraphicsGetCurrentContext(); 77 | 78 | CGContextScaleCTM(context, 1.0f, -1.0f); 79 | CGContextTranslateCTM(context, 0.0f, -(imageRect.size.height)); 80 | 81 | CGContextClipToMask(context, imageRect, self.CGImage); 82 | CGContextSetFillColorWithColor(context, color.CGColor); 83 | CGContextFillRect(context, imageRect); 84 | 85 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 86 | UIGraphicsEndImageContext(); 87 | 88 | return newImage; 89 | } 90 | 91 | @end -------------------------------------------------------------------------------- /Source/Views/DBMessagingTypingIndicatorFooterView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingTypingIndicatorFooterView.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-23. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingTypingIndicatorFooterView.h" 16 | 17 | #import "DBMessagingCollectionViewLayoutAttributes.h" 18 | 19 | @interface DBMessagingTypingIndicatorFooterView () 20 | { 21 | UIImageView *_typingIndicatorImageView; 22 | } 23 | 24 | @property (assign, nonatomic) UIEdgeInsets messageBubbleTextContainerInsets; 25 | @property (assign, nonatomic) CGSize incomingAvatarSize; 26 | @property (assign, nonatomic) CGSize outgoingAvatarSize; 27 | 28 | @end 29 | 30 | @implementation DBMessagingTypingIndicatorFooterView 31 | 32 | + (NSString *)viewReuseIdentifier { 33 | return NSStringFromClass([self class]); 34 | } 35 | 36 | - (instancetype)initWithFrame:(CGRect)frame 37 | { 38 | self = [super initWithFrame:frame]; 39 | if (self) { 40 | _typingIndicatorImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"TypingBubble1"]]; 41 | [_typingIndicatorImageView setFrame:CGRectMake(0, 0, CGRectGetHeight(self.frame), CGRectGetHeight(self.frame))]; 42 | [_typingIndicatorImageView setContentMode:UIViewContentModeScaleAspectFit]; 43 | [_typingIndicatorImageView setClipsToBounds:YES]; 44 | [_typingIndicatorImageView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth]; 45 | [self addSubview:_typingIndicatorImageView]; 46 | 47 | /* 48 | NSInteger numberOfImages = 3; 49 | NSMutableArray *animationImages = [[NSMutableArray alloc] init]; 50 | for (int i = 0; i < numberOfImages; i++) { 51 | UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"TypingBubble%d", i + 1]]; 52 | [animationImages addObject:image]; 53 | } 54 | _typingIndicatorImageView.animationImages = animationImages; 55 | _typingIndicatorImageView.animationRepeatCount = INT16_MAX; 56 | _typingIndicatorImageView.animationDuration = 1.0; 57 | */ 58 | } 59 | return self; 60 | } 61 | 62 | - (void)applyLayoutAttributes:(DBMessagingCollectionViewLayoutAttributes *)layoutAttributes 63 | { 64 | [super applyLayoutAttributes:layoutAttributes]; 65 | 66 | self.incomingAvatarSize = layoutAttributes.incomingAvatarViewSize; 67 | self.outgoingAvatarSize = layoutAttributes.outgoingAvatarViewSize; 68 | self.messageBubbleTextContainerInsets = layoutAttributes.messageBubbleTextViewTextContainerInsets; 69 | 70 | } 71 | 72 | - (void)layoutSubviews 73 | { 74 | [super layoutSubviews]; 75 | 76 | [_typingIndicatorImageView sizeToFit]; 77 | [_typingIndicatorImageView setFrame:CGRectMake(0, 0, CGRectGetWidth(_typingIndicatorImageView.frame), CGRectGetHeight(self.bounds) - 10.0)]; 78 | [_typingIndicatorImageView setCenter:CGPointMake(CGRectGetMidX(_typingIndicatorImageView.frame), CGRectGetHeight(self.frame) / 2.0)]; 79 | 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Source/Components/Photos/DBMessagingPhotoPickerPresentationController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingPhotoPickerPresentationController.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-10. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingPhotoPickerPresentationController.h" 16 | 17 | @interface DBMessagingPhotoPickerPresentationController () 18 | 19 | @property (strong, nonatomic) UIView *dimmingView; 20 | @property (strong, nonatomic) UITapGestureRecognizer *dimmingViewTap; 21 | 22 | @end 23 | 24 | @implementation DBMessagingPhotoPickerPresentationController 25 | 26 | #pragma mark - Getters 27 | 28 | - (UIView *)dimmingView { 29 | 30 | if (!_dimmingView) { 31 | _dimmingView = [[UIView alloc] init]; 32 | _dimmingView.frame = self.containerView.bounds; 33 | _dimmingView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 34 | _dimmingView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; 35 | _dimmingView.alpha = 0.0; 36 | 37 | _dimmingViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismiss:)]; 38 | [_dimmingView addGestureRecognizer:_dimmingViewTap]; 39 | } 40 | 41 | return _dimmingView; 42 | } 43 | 44 | - (void)presentationTransitionWillBegin { 45 | [super presentationTransitionWillBegin]; 46 | 47 | // Add the dimming view and the presented view to the heirarchy 48 | [self.containerView addSubview:self.dimmingView]; 49 | [self.containerView addSubview:self.presentedView]; 50 | 51 | // Fade in the dimming view alongside the transition 52 | id transitionCoordinator = self.presentingViewController.transitionCoordinator; 53 | 54 | [transitionCoordinator animateAlongsideTransition:^(id context) { 55 | self.dimmingView.alpha = 1.0; 56 | } completion:nil]; 57 | } 58 | 59 | - (void)presentationTransitionDidEnd:(BOOL)completed { 60 | [super presentationTransitionDidEnd:completed]; 61 | 62 | // If the presentation didn't complete, remove the dimming view 63 | if (!completed) { 64 | [self.dimmingView removeFromSuperview]; 65 | } 66 | } 67 | 68 | - (void)dismissalTransitionWillBegin { 69 | [super dismissalTransitionWillBegin]; 70 | 71 | // Fade out the dimming view alongside the transition 72 | id transitionCoordinator = self.presentingViewController.transitionCoordinator; 73 | 74 | [transitionCoordinator animateAlongsideTransition:^(id context) { 75 | self.dimmingView.alpha = 0.0; 76 | } completion:nil]; 77 | } 78 | 79 | - (void)dismissalTransitionDidEnd:(BOOL)completed { 80 | [super dismissalTransitionDidEnd:completed]; 81 | 82 | // If the dismissal completed, remove the dimming view 83 | if (completed) { 84 | [self.dimmingView removeFromSuperview]; 85 | } 86 | } 87 | 88 | - (CGRect)frameOfPresentedViewInContainerView { 89 | CGRect superFrame = [super frameOfPresentedViewInContainerView]; 90 | 91 | CGRect frame = superFrame; 92 | frame.size.height = MAX(290.0, superFrame.size.height * 0.7); 93 | frame.origin.y = superFrame.size.height - frame.size.height; 94 | return frame; 95 | } 96 | 97 | #pragma mark - Actions 98 | 99 | - (void)dismiss:(id)sender { 100 | [self.presentedViewController dismissViewControllerAnimated:YES completion:nil]; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewLayoutAttributes.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewLayoutAttributes.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-22. 10 | // Copyright (c) 2014 Devon Boyer . All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | /** 18 | * A 'DBMessagingCollectionViewLayoutAttributes' object manages the layout-related attributes 19 | * for views in a 'DBMessagingCollectionView'. 20 | */ 21 | @interface DBMessagingCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes 22 | 23 | /** 24 | * The distance that the cell is offset when using 'DBMessagingTimestampStyleSliding'. 25 | * 26 | * @discussion This attribute is only used when using 'DBMessagingTimestampStyleSliding'. 27 | */ 28 | @property (assign, nonatomic) CGFloat slidingTimestampDistance; 29 | 30 | /** 31 | * The distance that the avatar is offset when using 'DBMessagingTimestampStyleSliding'. 32 | * 33 | * @discussion This attribute is only used when using 'DBMessagingTimestampStyleSliding'. 34 | */ 35 | @property (assign, nonatomic) CGFloat slidingTimestampAvatarDistance; 36 | 37 | /** 38 | * The font used to display the message text. 39 | * 40 | * @warning The messageBubbleFont cannot be 'nil'. 41 | */ 42 | @property (strong, nonatomic) UIFont *messageBubbleFont; 43 | 44 | /** 45 | * The height of the message bubble's top label. 46 | */ 47 | @property (assign, nonatomic) CGFloat messageBubbleTopLabelHeight; 48 | 49 | /** 50 | * The height of the cell top label. 51 | */ 52 | @property (assign, nonatomic) CGFloat cellTopLabelHeight; 53 | 54 | /** 55 | * The height of the cell bottom label. 56 | */ 57 | @property (assign, nonatomic) CGFloat cellBottomLabelHeight; 58 | 59 | /** 60 | * The size of the avatar for incoming messages. 61 | * 62 | * @warning The width of the incoming avatar must be equal to the height. 63 | */ 64 | @property (assign, nonatomic) CGSize incomingAvatarViewSize; 65 | 66 | /** 67 | * The size of the avatar for outgoing messages. 68 | * 69 | * @warning The width of the outgoing avatar must be equal to the height. 70 | */ 71 | @property (assign, nonatomic) CGSize outgoingAvatarViewSize; 72 | 73 | /** 74 | * The minimum amount of space between the messageBubble and the edge of the screen. 75 | * The default value is 70.0. 76 | */ 77 | @property (assign, nonatomic) CGFloat messageBubbleLeftRightMargin; 78 | 79 | /** 80 | * The amount of spacing inbetween the avatar and message bubble for incoming messages. 81 | * 82 | * @discussion The spacing will be set to 0.0 if incomingAvatarSize.width is set to 0.0. 83 | */ 84 | @property (assign, nonatomic) CGFloat incomingMessageBubbleAvatarSpacing; 85 | 86 | /** 87 | * The amount of spacing inbetween the avatar and message bubble for outgoing messages. 88 | * 89 | * @discussion The spacing will be set to 0.0 if outgoingAvatarSize.width is set to 0.0. 90 | */ 91 | @property (assign, nonatomic) CGFloat outgoingMessageBubbleAvatarSpacing; 92 | 93 | /** 94 | * The inset of the text container's layout area within the text view's content area in a 'DBMessagingTextCell'. 95 | * The specified inset values should be greater than or equal to 0.0. 96 | */ 97 | @property (assign, nonatomic) UIEdgeInsets messageBubbleTextViewTextContainerInsets; 98 | 99 | /** 100 | * The size of the image for incoming image messages. 101 | * 102 | * @discussion The width value is used to determine the height that will maintain the same aspect ratio. 103 | * The height value will be used as the maximum height. 104 | */ 105 | @property (assign, nonatomic) CGSize mediaViewSize; 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /MessagingKit/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Source/Factories/DBMessagingTimestampFormatter.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingTimestampFormatter.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-25. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | /** 18 | * An instance of 'DBMessagingTimestampFormatter' is a singleton object that provides an efficient means 19 | * for creating attributed and non-attributed string representations of 'NSDate' objects. 20 | * It is intended to be used as the method by which you display timestamps in a 'DBMessagingCollectionView'. 21 | */ 22 | @interface DBMessagingTimestampFormatter : NSObject 23 | 24 | /** 25 | * Returns the cached date formatter object used by the 'IGChatTimestampFormatter' shared instance. 26 | */ 27 | @property (strong, nonatomic, readonly) NSDateFormatter *dateFormatter; 28 | 29 | /** 30 | * The text attributes to apply to the day, month, and year components of the string representation of a given date. 31 | * The default value is a dictionary containing attributes that specify centered, light gray text and the bold system font at size '12.0f'. 32 | */ 33 | @property (copy, nonatomic) NSDictionary *dateTextAttributes; 34 | 35 | /** 36 | * The text attributes to apply to the minute and hour componenents of the string representation of a given date. 37 | * The default value is a dictionary containing attributes that specify centered, light gray text and the system font at size '12.0f'. 38 | */ 39 | @property (copy, nonatomic) NSDictionary *timeTextAttributes; 40 | 41 | 42 | + (instancetype)sharedFormatter; 43 | 44 | /** 45 | * Returns a string representation of the given date formatted in the current locale using 'NSDateFormatterMediumStyle' for the date style 46 | * and 'NSDateFormatterShortStyle' for the time style. It uses relative date formatting where possible. 47 | * 48 | * @param date The date to format. 49 | * 50 | * @return A formatted string representation of date. 51 | */ 52 | - (NSString *)timestampForDate:(NSDate *)date; 53 | 54 | /** 55 | * Returns an attributed string representation of the given date formatted as described in 'timestampForDate:'. 56 | * It applies the attributes in 'dateTextAttributes' and 'timeTextAttributes', respectively. 57 | * 58 | * @param date The date to format. 59 | * 60 | * @return A formatted, attributed string representation of date. 61 | * 62 | * @see 'timestampForDate:'. 63 | * @see 'dateTextAttributes'. 64 | * @see 'timeTextAttributes'. 65 | */ 66 | - (NSAttributedString *)attributedTimestampForDate:(NSDate *)date; 67 | 68 | /** 69 | * Returns a string representation of *only* the minute and hour components of the given date formatted in the current locale styled using 'NSDateFormatterShortStyle'. 70 | * 71 | * @param date The date to format. 72 | * 73 | * @return A formatted string representation of the minute and hour components of date. 74 | */ 75 | - (NSString *)timeForDate:(NSDate *)date; 76 | 77 | /** 78 | * Returns a string representation of *only* the day, month, and year components of the given date formatted in the current locale styled using 'NSDateFormatterMediumStyle'. 79 | * 80 | * @param date The date to format. 81 | * 82 | * @return A formatted string representation of the day, month, and year components of date. 83 | */ 84 | - (NSString *)relativeDateForDate:(NSDate *)date; 85 | 86 | /** 87 | * Returns a string representation of day, month, and year components of the given date formatted in the current locale. 88 | * 89 | * @param date The date to format. 90 | * 91 | * @return A formatted string representation of the day, month, and time components. 92 | */ 93 | - (NSString *)verboseTimestampForDate:(NSDate *)date; 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /MessagingKit/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "8.0", 8 | "subtype" : "736h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "8.0", 16 | "subtype" : "736h", 17 | "scale" : "3x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "8.0", 24 | "subtype" : "667h", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "orientation" : "portrait", 29 | "idiom" : "iphone", 30 | "extent" : "full-screen", 31 | "minimum-system-version" : "7.0", 32 | "scale" : "2x" 33 | }, 34 | { 35 | "orientation" : "portrait", 36 | "idiom" : "iphone", 37 | "extent" : "full-screen", 38 | "minimum-system-version" : "7.0", 39 | "subtype" : "retina4", 40 | "scale" : "2x" 41 | }, 42 | { 43 | "orientation" : "portrait", 44 | "idiom" : "ipad", 45 | "extent" : "full-screen", 46 | "minimum-system-version" : "7.0", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "orientation" : "landscape", 51 | "idiom" : "ipad", 52 | "extent" : "full-screen", 53 | "minimum-system-version" : "7.0", 54 | "scale" : "1x" 55 | }, 56 | { 57 | "orientation" : "portrait", 58 | "idiom" : "ipad", 59 | "extent" : "full-screen", 60 | "minimum-system-version" : "7.0", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "orientation" : "landscape", 65 | "idiom" : "ipad", 66 | "extent" : "full-screen", 67 | "minimum-system-version" : "7.0", 68 | "scale" : "2x" 69 | }, 70 | { 71 | "orientation" : "portrait", 72 | "idiom" : "iphone", 73 | "extent" : "full-screen", 74 | "scale" : "1x" 75 | }, 76 | { 77 | "orientation" : "portrait", 78 | "idiom" : "iphone", 79 | "extent" : "full-screen", 80 | "scale" : "2x" 81 | }, 82 | { 83 | "orientation" : "portrait", 84 | "idiom" : "iphone", 85 | "extent" : "full-screen", 86 | "subtype" : "retina4", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "orientation" : "portrait", 91 | "idiom" : "ipad", 92 | "extent" : "to-status-bar", 93 | "scale" : "1x" 94 | }, 95 | { 96 | "orientation" : "portrait", 97 | "idiom" : "ipad", 98 | "extent" : "full-screen", 99 | "scale" : "1x" 100 | }, 101 | { 102 | "orientation" : "landscape", 103 | "idiom" : "ipad", 104 | "extent" : "to-status-bar", 105 | "scale" : "1x" 106 | }, 107 | { 108 | "orientation" : "landscape", 109 | "idiom" : "ipad", 110 | "extent" : "full-screen", 111 | "scale" : "1x" 112 | }, 113 | { 114 | "orientation" : "portrait", 115 | "idiom" : "ipad", 116 | "extent" : "to-status-bar", 117 | "scale" : "2x" 118 | }, 119 | { 120 | "orientation" : "portrait", 121 | "idiom" : "ipad", 122 | "extent" : "full-screen", 123 | "scale" : "2x" 124 | }, 125 | { 126 | "orientation" : "landscape", 127 | "idiom" : "ipad", 128 | "extent" : "to-status-bar", 129 | "scale" : "2x" 130 | }, 131 | { 132 | "orientation" : "landscape", 133 | "idiom" : "ipad", 134 | "extent" : "full-screen", 135 | "scale" : "2x" 136 | } 137 | ], 138 | "info" : { 139 | "version" : 1, 140 | "author" : "xcode" 141 | } 142 | } -------------------------------------------------------------------------------- /Source/Views/DBMessagingTimestampSupplementaryView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingTimestampSupplementaryView.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-11. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingTimestampSupplementaryView.h" 16 | #import "DBMessagingCollectionViewLayoutAttributes.h" 17 | 18 | @interface DBMessagingTimestampSupplementaryView () 19 | 20 | @property (assign, nonatomic) CGFloat messageBubbleTopLabelHeight; 21 | @property (assign, nonatomic) CGFloat cellTopLabelHeight; 22 | @property (assign, nonatomic) CGSize incomingAvatarSize; 23 | @property (assign, nonatomic) CGSize outgoingAvatarSize; 24 | @property (assign, nonatomic) UIEdgeInsets messageBubbleTextContainerInsets; 25 | 26 | @property (strong, nonatomic) UILabel *timestampLabel; 27 | 28 | @end 29 | 30 | @implementation DBMessagingTimestampSupplementaryView 31 | 32 | + (NSString *)viewReuseIdentifier { 33 | return NSStringFromClass([self class]); 34 | } 35 | 36 | - (instancetype)initWithFrame:(CGRect)frame 37 | { 38 | self = [super initWithFrame:frame]; 39 | if (self) { 40 | _timestampLabel = [[UILabel alloc] initWithFrame:self.bounds]; 41 | [_timestampLabel setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; 42 | [_timestampLabel setTextAlignment:NSTextAlignmentCenter]; 43 | [_timestampLabel setNumberOfLines:1]; 44 | [self addSubview:_timestampLabel]; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)layoutSubviews 50 | { 51 | [super layoutSubviews]; 52 | 53 | switch (_timestampStyle) { 54 | case DBMessagingTimestampStyleHidden: { 55 | 56 | switch (self.type) { 57 | case MessageBubbleTypeIncoming: { 58 | [self.timestampLabel setTextAlignment:NSTextAlignmentLeft]; 59 | 60 | [_timestampLabel setFrame:CGRectMake(self.incomingAvatarSize.width + self.messageBubbleTextContainerInsets.right + self.messageBubbleTextContainerInsets.left, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds))]; 61 | break; 62 | } 63 | case MessageBubbleTypeOutgoing: { 64 | [self.timestampLabel setTextAlignment:NSTextAlignmentRight]; 65 | 66 | [_timestampLabel setFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds) - self.outgoingAvatarSize.width - self.messageBubbleTextContainerInsets.right - self.messageBubbleTextContainerInsets.left, CGRectGetHeight(self.bounds))]; 67 | break; 68 | } 69 | } 70 | 71 | break; 72 | } 73 | case DBMessagingTimestampStyleSliding: { 74 | [self.timestampLabel setTextAlignment:NSTextAlignmentCenter]; 75 | 76 | // Center the timestamp relative the the message bubble 77 | CGFloat relativeHeight = self.bounds.size.height + _messageBubbleTopLabelHeight + _cellTopLabelHeight; 78 | 79 | CGSize timestampLabelSize = [_timestampLabel sizeThatFits:self.bounds.size]; 80 | [_timestampLabel setFrame:CGRectMake(0, 0, self.bounds.size.width, timestampLabelSize.height)]; 81 | [_timestampLabel setCenter:CGPointMake(_timestampLabel.center.x, relativeHeight / 2.0)]; 82 | break; 83 | } 84 | default: 85 | break; 86 | } 87 | 88 | } 89 | 90 | - (void)applyLayoutAttributes:(DBMessagingCollectionViewLayoutAttributes *)layoutAttributes 91 | { 92 | [super applyLayoutAttributes:layoutAttributes]; 93 | 94 | self.incomingAvatarSize = layoutAttributes.incomingAvatarViewSize; 95 | self.outgoingAvatarSize = layoutAttributes.outgoingAvatarViewSize; 96 | self.messageBubbleTextContainerInsets = layoutAttributes.messageBubbleTextViewTextContainerInsets; 97 | self.messageBubbleTopLabelHeight = layoutAttributes.messageBubbleTopLabelHeight; 98 | self.cellTopLabelHeight = layoutAttributes.cellTopLabelHeight; 99 | } 100 | 101 | - (void)prepareForReuse 102 | { 103 | [super prepareForReuse]; 104 | 105 | self.timestampLabel.text = @""; 106 | self.timestampLabel.attributedText = nil; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewLayoutAttributes.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewLayoutAttributes.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-22. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingCollectionViewLayoutAttributes.h" 16 | 17 | #pragma mark - Setters 18 | 19 | @implementation DBMessagingCollectionViewLayoutAttributes 20 | 21 | - (void)setMessageBubbleFont:(UIFont *)messageBubbleFont 22 | { 23 | NSParameterAssert(messageBubbleFont != nil); 24 | _messageBubbleFont = messageBubbleFont; 25 | } 26 | 27 | - (void)setMessageBubbleLeftRightMargin:(CGFloat)messageBubbleLeftRightMargin 28 | { 29 | NSParameterAssert(messageBubbleLeftRightMargin >= 0.0f); 30 | _messageBubbleLeftRightMargin = ceilf(messageBubbleLeftRightMargin); 31 | } 32 | 33 | - (void)setMessageBubbleTopLabelHeight:(CGFloat)messageBubbleTopLabelHeight 34 | { 35 | NSParameterAssert(messageBubbleTopLabelHeight >= 0); 36 | _messageBubbleTopLabelHeight = floorf(messageBubbleTopLabelHeight); 37 | } 38 | 39 | - (void)setCellTopLabelHeight:(CGFloat)messageTopLabelHeight 40 | { 41 | NSParameterAssert(messageTopLabelHeight >= 0); 42 | _cellTopLabelHeight = floorf(messageTopLabelHeight); 43 | } 44 | 45 | - (void)setCellBottomLabelHeight:(CGFloat)messageBottomLabelHeight 46 | { 47 | NSParameterAssert(messageBottomLabelHeight >= 0); 48 | _cellBottomLabelHeight = floorf(messageBottomLabelHeight); 49 | } 50 | 51 | - (void)setIncomingAvatarViewSize:(CGSize)incomingAvatarViewSize 52 | { 53 | NSParameterAssert(incomingAvatarViewSize.width >= 0.0f && incomingAvatarViewSize.height >= 0.0f); 54 | NSParameterAssert(incomingAvatarViewSize.height == incomingAvatarViewSize.width); 55 | _incomingAvatarViewSize = CGSizeMake(ceil(incomingAvatarViewSize.width), ceilf(incomingAvatarViewSize.height)); 56 | } 57 | 58 | - (void)setOutgoingAvatarViewSize:(CGSize)outgoingAvatarViewSize 59 | { 60 | NSParameterAssert(outgoingAvatarViewSize.width >= 0.0f && outgoingAvatarViewSize.height >= 0.0f); 61 | NSParameterAssert(outgoingAvatarViewSize.height == outgoingAvatarViewSize.width); 62 | _outgoingAvatarViewSize = CGSizeMake(ceil(outgoingAvatarViewSize.width), ceilf(outgoingAvatarViewSize.height)); 63 | } 64 | 65 | - (void)setMediaViewSize:(CGSize)mediaViewSize 66 | { 67 | NSParameterAssert(mediaViewSize.width >= 0.0f && mediaViewSize.height >= 0.0f); 68 | _mediaViewSize = CGSizeMake(ceil(mediaViewSize.width), ceilf(mediaViewSize.height)); 69 | } 70 | 71 | - (void)setIncomingMessageBubbleAvatarSpacing:(CGFloat)incomingMessageBubbleAvatarSpacing 72 | { 73 | NSParameterAssert(incomingMessageBubbleAvatarSpacing >= 0.0f); 74 | _incomingMessageBubbleAvatarSpacing = incomingMessageBubbleAvatarSpacing; 75 | } 76 | 77 | - (void)setOutgoingMessageBubbleAvatarSpacing:(CGFloat)outgoingMessageBubbleAvatarSpacing 78 | { 79 | NSParameterAssert(outgoingMessageBubbleAvatarSpacing >= 0.0f); 80 | _outgoingMessageBubbleAvatarSpacing = outgoingMessageBubbleAvatarSpacing; 81 | } 82 | 83 | #pragma mark - NSCopying 84 | 85 | - (instancetype)copyWithZone:(NSZone *)zone 86 | { 87 | DBMessagingCollectionViewLayoutAttributes *copy = [super copyWithZone:zone]; 88 | 89 | if (copy.representedElementCategory != UICollectionElementCategoryCell) { 90 | return copy; 91 | } 92 | 93 | copy.messageBubbleFont = self.messageBubbleFont; 94 | copy.messageBubbleLeftRightMargin = self.messageBubbleLeftRightMargin; 95 | copy.messageBubbleTopLabelHeight = self.messageBubbleTopLabelHeight; 96 | copy.cellTopLabelHeight = self.cellTopLabelHeight; 97 | copy.cellBottomLabelHeight = self.cellBottomLabelHeight; 98 | copy.messageBubbleTextViewTextContainerInsets = self.messageBubbleTextViewTextContainerInsets; 99 | copy.incomingAvatarViewSize = self.incomingAvatarViewSize; 100 | copy.outgoingAvatarViewSize = self.outgoingAvatarViewSize; 101 | copy.mediaViewSize = self.mediaViewSize; 102 | copy.incomingMessageBubbleAvatarSpacing = self.incomingMessageBubbleAvatarSpacing; 103 | copy.outgoingMessageBubbleAvatarSpacing = self.outgoingMessageBubbleAvatarSpacing; 104 | 105 | copy.slidingTimestampDistance = self.slidingTimestampDistance; 106 | copy.slidingTimestampAvatarDistance = self.slidingTimestampAvatarDistance; 107 | 108 | return copy; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Source/Controllers/DBSystemSoundPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBSystemSoundPlayer.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/MessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-28. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBSystemSoundPlayer.h" 16 | #import 17 | #import 18 | 19 | @interface DBSystemSoundPlayer () 20 | 21 | - (void)_playSoundWithName:(NSString *)filename 22 | extension:(NSString *)extension 23 | isAlert:(BOOL)isAlert; 24 | 25 | - (SystemSoundID)_createSoundIDWithName:(NSString *)filename 26 | extension:(NSString *)extension; 27 | 28 | - (void)_logError:(OSStatus)error withMessage:(NSString *)message; 29 | 30 | @end 31 | 32 | @implementation DBSystemSoundPlayer 33 | 34 | + (DBSystemSoundPlayer *)sharedPlayer 35 | { 36 | static DBSystemSoundPlayer *sharedPlayer; 37 | 38 | static dispatch_once_t onceToken; 39 | dispatch_once(&onceToken, ^{ 40 | sharedPlayer = [[DBSystemSoundPlayer alloc] init]; 41 | }); 42 | 43 | return sharedPlayer; 44 | } 45 | 46 | #pragma mark - Public 47 | 48 | - (void)playSoundWithName:(NSString *)filename fileExtension:(NSString *)extension 49 | { 50 | [self _playSoundWithName:filename 51 | extension:extension 52 | isAlert:NO]; 53 | } 54 | 55 | - (void)playAlertSoundWithName:(NSString *)filename fileExtension:(NSString *)extension 56 | { 57 | [self _playSoundWithName:filename 58 | extension:extension 59 | isAlert:YES]; 60 | } 61 | 62 | - (void)playVibrateSound 63 | { 64 | AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); 65 | } 66 | 67 | #pragma mark - Private 68 | 69 | - (void)_playSoundWithName:(NSString *)filename 70 | extension:(NSString *)extension 71 | isAlert:(BOOL)isAlert 72 | { 73 | if (!filename || !extension) { 74 | return; 75 | } 76 | 77 | SystemSoundID soundID = [self _createSoundIDWithName:filename extension:extension]; 78 | 79 | if (soundID) { 80 | if (isAlert) { 81 | AudioServicesPlayAlertSound(soundID); 82 | } 83 | else { 84 | AudioServicesPlaySystemSound(soundID); 85 | } 86 | } 87 | else { 88 | NSLog(@"Sound could not be played"); 89 | } 90 | } 91 | 92 | #pragma mark - Managing sounds 93 | 94 | - (SystemSoundID)_createSoundIDWithName:(NSString *)filename 95 | extension:(NSString *)extension 96 | { 97 | NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename 98 | withExtension:extension]; 99 | 100 | if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) { 101 | SystemSoundID soundID; 102 | OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileURL, &soundID); 103 | 104 | if (error) { 105 | [self _logError:error withMessage:@"Warning! SystemSoundID could not be created."]; 106 | return 0; 107 | } 108 | else { 109 | return soundID; 110 | } 111 | } 112 | 113 | NSLog(@"[%@] Error: audio file not found at URL: %@", [self class], fileURL); 114 | return 0; 115 | } 116 | 117 | - (void)_logError:(OSStatus)error withMessage:(NSString *)message 118 | { 119 | NSString *errorMessage = nil; 120 | 121 | switch (error) { 122 | case kAudioServicesUnsupportedPropertyError: 123 | errorMessage = @"The property is not supported."; 124 | break; 125 | case kAudioServicesBadPropertySizeError: 126 | errorMessage = @"The size of the property data was not correct."; 127 | break; 128 | case kAudioServicesBadSpecifierSizeError: 129 | errorMessage = @"The size of the specifier data was not correct."; 130 | break; 131 | case kAudioServicesSystemSoundUnspecifiedError: 132 | errorMessage = @"An unspecified error has occurred."; 133 | break; 134 | case kAudioServicesSystemSoundClientTimedOutError: 135 | errorMessage = @"System sound client message timed out."; 136 | break; 137 | } 138 | 139 | NSLog(@"[%@] %@ Error: (code %d) %@", [self class], message, (int)error, errorMessage); 140 | } 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /Source/Components/Photos/DBMessagingPhotoPickerController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingMediaCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingMediaCell.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-09. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingMediaCell.h" 16 | #import "DBMessagingCollectionViewLayoutAttributes.h" 17 | #import "UIColor+Messaging.h" 18 | 19 | @interface DBMessagingMediaCell () 20 | 21 | @property (strong, nonatomic) UITapGestureRecognizer *mediaTap; 22 | 23 | @property (assign, nonatomic) CGSize mediaViewSize; 24 | 25 | @end 26 | 27 | @implementation DBMessagingMediaCell 28 | 29 | + (NSString *)mimeType { 30 | NSAssert(false, @"%s must be overridden by subclass", __PRETTY_FUNCTION__); 31 | return nil; 32 | } 33 | 34 | + (NSString *)cellReuseIdentifier { 35 | NSAssert(false, @"%s must be overridden by subclass", __PRETTY_FUNCTION__); 36 | return nil; 37 | } 38 | 39 | - (instancetype)initWithFrame:(CGRect)frame 40 | { 41 | self = [super initWithFrame:frame]; 42 | if (self) { 43 | _mediaTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMediaTap:)]; 44 | [_mediaTap setDelegate:self]; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)applyLayoutAttributes:(DBMessagingCollectionViewLayoutAttributes *)layoutAttributes 50 | { 51 | [super applyLayoutAttributes:layoutAttributes]; 52 | 53 | self.mediaViewSize = layoutAttributes.mediaViewSize; 54 | } 55 | 56 | - (void)layoutSubviews 57 | { 58 | [super layoutSubviews]; 59 | 60 | CGFloat imageWidth = self.mediaViewSize.width; 61 | 62 | switch (self.type) { 63 | case MessageBubbleTypeIncoming: { 64 | 65 | [self.messageBubbleImageView setFrame:CGRectMake(self.incomingAvatarSize.width + self.incomingMessageBubbleAvatarSpacing, 66 | CGRectGetMaxY(self.messageTopLabel.frame), 67 | imageWidth, 68 | CGRectGetHeight(self.frame) - self.cellTopLabelHeight - self.messageTopLabelHeight - self.cellBottomLabelHeight)]; 69 | break; 70 | } 71 | case MessageBubbleTypeOutgoing: { 72 | 73 | [self.messageBubbleImageView setFrame:CGRectMake(CGRectGetWidth(self.frame) - imageWidth - self.outgoingAvatarSize.width - self.outgoingMessageBubbleAvatarSpacing, 74 | CGRectGetMaxY(self.messageTopLabel.frame), 75 | imageWidth, 76 | CGRectGetHeight(self.frame) - self.cellTopLabelHeight - self.messageTopLabelHeight - self.cellBottomLabelHeight)]; 77 | break; 78 | } 79 | default: 80 | break; 81 | } 82 | 83 | [self applyMask]; 84 | } 85 | 86 | #pragma mark - Setters 87 | 88 | - (void)setMediaView:(UIView *)mediaView { 89 | _mediaView = mediaView; 90 | [_mediaView setFrame:self.messageBubbleImageView.frame]; 91 | [_mediaView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; 92 | [_mediaView setClipsToBounds:YES]; 93 | [_mediaView addGestureRecognizer:_mediaTap]; 94 | [self.messageBubbleImageView addSubview:_mediaView]; 95 | } 96 | 97 | #pragma mark - Actions 98 | 99 | - (void)handleMediaTap:(UITapGestureRecognizer *)tap 100 | { 101 | if ([self.delegate respondsToSelector:@selector(messageCell:didTapMediaView:)]) { 102 | [self.delegate messageCell:self didTapMediaView:_mediaView]; 103 | } 104 | } 105 | 106 | #pragma mark - UIGestureRecognizerDelegate 107 | 108 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 109 | return YES; 110 | } 111 | 112 | #pragma mark - Utility 113 | 114 | - (void)applyMask { 115 | 116 | CALayer *maskingLayer = _mediaView.layer; 117 | CGPoint center = _mediaView.center; 118 | 119 | UIImage *maskImage = self.messageBubbleImageView.image; 120 | 121 | CALayer *mask = [CALayer layer]; 122 | mask.contents = (id)[maskImage CGImage]; 123 | mask.frame = maskingLayer.bounds; 124 | mask.contentsScale = [UIScreen mainScreen].scale; 125 | mask.contentsCenter = CGRectMake(center.x/maskingLayer.bounds.size.width, 126 | center.y/maskingLayer.bounds.size.height, 127 | 1.0/maskingLayer.bounds.size.width, 128 | 1.0/maskingLayer.bounds.size.height); 129 | 130 | self.messageBubbleImageView.layer.mask = mask; 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /Source/Factories/DBMessagingTimestampFormatter.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingTimestampFormatter.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-25. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingTimestampFormatter.h" 16 | 17 | @interface DBMessagingTimestampFormatter () 18 | 19 | @property (strong, nonatomic, readwrite) NSDateFormatter *dateFormatter; 20 | 21 | @end 22 | 23 | @implementation DBMessagingTimestampFormatter 24 | 25 | #pragma mark - Singleton 26 | 27 | + (instancetype)sharedFormatter 28 | { 29 | static DBMessagingTimestampFormatter *sharedFormatter = nil; 30 | static dispatch_once_t onceToken = 0; 31 | dispatch_once(&onceToken, ^{ 32 | sharedFormatter = [[self alloc] init]; 33 | }); 34 | 35 | return sharedFormatter; 36 | } 37 | 38 | #pragma mark - Initialization 39 | 40 | - (instancetype)init 41 | { 42 | self = [super init]; 43 | if (self) { 44 | _dateFormatter = [[NSDateFormatter alloc] init]; 45 | [_dateFormatter setLocale:[NSLocale currentLocale]]; 46 | [_dateFormatter setDoesRelativeDateFormatting:YES]; 47 | 48 | UIColor *color = [UIColor lightGrayColor]; 49 | 50 | NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 51 | paragraphStyle.alignment = NSTextAlignmentCenter; 52 | 53 | _dateTextAttributes = @{ NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0f], 54 | NSForegroundColorAttributeName : color, 55 | NSParagraphStyleAttributeName : paragraphStyle }; 56 | 57 | _timeTextAttributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:12.0f], 58 | NSForegroundColorAttributeName : color, 59 | NSParagraphStyleAttributeName : paragraphStyle }; 60 | } 61 | return self; 62 | } 63 | 64 | 65 | #pragma mark - Formatter 66 | 67 | - (NSString *)timestampForDate:(NSDate *)date 68 | { 69 | if (!date) { 70 | return nil; 71 | } 72 | 73 | [self.dateFormatter setDateStyle:NSDateFormatterMediumStyle]; 74 | [self.dateFormatter setTimeStyle:NSDateFormatterShortStyle]; 75 | return [self.dateFormatter stringFromDate:date]; 76 | } 77 | 78 | - (NSAttributedString *)attributedTimestampForDate:(NSDate *)date 79 | { 80 | if (!date) { 81 | return nil; 82 | } 83 | 84 | [_dateFormatter setDoesRelativeDateFormatting:YES]; 85 | 86 | NSString *relativeDate = [self relativeDateForDate:date]; 87 | NSString *time = [self timeForDate:date]; 88 | 89 | NSMutableAttributedString *timestamp = [[NSMutableAttributedString alloc] initWithString:relativeDate 90 | attributes:self.dateTextAttributes]; 91 | 92 | [timestamp appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; 93 | 94 | [timestamp appendAttributedString:[[NSAttributedString alloc] initWithString:time 95 | attributes:self.timeTextAttributes]]; 96 | 97 | return [[NSAttributedString alloc] initWithAttributedString:timestamp]; 98 | } 99 | 100 | - (NSString *)timeForDate:(NSDate *)date 101 | { 102 | if (!date) { 103 | return nil; 104 | } 105 | 106 | [_dateFormatter setDoesRelativeDateFormatting:YES]; 107 | 108 | [self.dateFormatter setDateStyle:NSDateFormatterNoStyle]; 109 | [self.dateFormatter setTimeStyle:NSDateFormatterShortStyle]; 110 | return [self.dateFormatter stringFromDate:date]; 111 | } 112 | 113 | - (NSString *)relativeDateForDate:(NSDate *)date 114 | { 115 | if (!date) { 116 | return nil; 117 | } 118 | 119 | [_dateFormatter setDoesRelativeDateFormatting:YES]; 120 | 121 | [self.dateFormatter setDateStyle:NSDateFormatterMediumStyle]; 122 | [self.dateFormatter setTimeStyle:NSDateFormatterNoStyle]; 123 | return [self.dateFormatter stringFromDate:date]; 124 | } 125 | 126 | - (NSString *)verboseTimestampForDate:(NSDate *)date 127 | { 128 | if (!date) { 129 | return nil; 130 | } 131 | 132 | [_dateFormatter setDoesRelativeDateFormatting:NO]; 133 | 134 | NSInteger year = [[[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:date] year]; 135 | NSInteger currentYear = [[[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:[NSDate date]] year]; 136 | 137 | if (year == currentYear) { 138 | [self.dateFormatter setDateFormat:@"EEE, MMM dd, h:mm a"]; 139 | } 140 | else { 141 | [self.dateFormatter setDateFormat:@"EEE, MMM dd, YYYY hh:mm a"]; 142 | } 143 | 144 | return [NSString stringWithFormat:@"Sent on %@", [self.dateFormatter stringFromDate:date]]; 145 | } 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /Source/Categories/UIImage+AnimatedGIF.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+AnimatedGIF.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/mayoff/uiimage-from-animated-gif 7 | // 8 | // 9 | // Created by Rob Mayoff on 2012-01-27. 10 | // Copyright (c) 2014 Rob Mayoff. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "UIImage+AnimatedGIF.h" 16 | #import 17 | 18 | #if __has_feature(objc_arc) 19 | #define toCF (__bridge CFTypeRef) 20 | #define fromCF (__bridge id) 21 | #else 22 | #define toCF (CFTypeRef) 23 | #define fromCF (id) 24 | #endif 25 | 26 | @implementation UIImage (AnimatedGIF) 27 | 28 | static int delayCentisecondsForImageAtIndex(CGImageSourceRef const source, size_t const i) { 29 | int delayCentiseconds = 1; 30 | CFDictionaryRef const properties = CGImageSourceCopyPropertiesAtIndex(source, i, NULL); 31 | if (properties) { 32 | CFDictionaryRef const gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary); 33 | if (gifProperties) { 34 | NSNumber *number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime); 35 | if (number == NULL || [number doubleValue] == 0) { 36 | number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime); 37 | } 38 | if ([number doubleValue] > 0) { 39 | // Even though the GIF stores the delay as an integer number of centiseconds, ImageIO “helpfully” converts that to seconds for us. 40 | delayCentiseconds = (int)lrint([number doubleValue] * 100); 41 | } 42 | } 43 | CFRelease(properties); 44 | } 45 | return delayCentiseconds; 46 | } 47 | 48 | static void createImagesAndDelays(CGImageSourceRef source, size_t count, CGImageRef imagesOut[count], int delayCentisecondsOut[count]) { 49 | for (size_t i = 0; i < count; ++i) { 50 | imagesOut[i] = CGImageSourceCreateImageAtIndex(source, i, NULL); 51 | delayCentisecondsOut[i] = delayCentisecondsForImageAtIndex(source, i); 52 | } 53 | } 54 | 55 | static int sum(size_t const count, int const *const values) { 56 | int theSum = 0; 57 | for (size_t i = 0; i < count; ++i) { 58 | theSum += values[i]; 59 | } 60 | return theSum; 61 | } 62 | 63 | static int pairGCD(int a, int b) { 64 | if (a < b) 65 | return pairGCD(b, a); 66 | while (true) { 67 | int const r = a % b; 68 | if (r == 0) 69 | return b; 70 | a = b; 71 | b = r; 72 | } 73 | } 74 | 75 | static int vectorGCD(size_t const count, int const *const values) { 76 | int gcd = values[0]; 77 | for (size_t i = 1; i < count; ++i) { 78 | // Note that after I process the first few elements of the vector, `gcd` will probably be smaller than any remaining element. By passing the smaller value as the second argument to `pairGCD`, I avoid making it swap the arguments. 79 | gcd = pairGCD(values[i], gcd); 80 | } 81 | return gcd; 82 | } 83 | 84 | static NSArray *frameArray(size_t const count, CGImageRef const images[count], int const delayCentiseconds[count], int const totalDurationCentiseconds) { 85 | int const gcd = vectorGCD(count, delayCentiseconds); 86 | size_t const frameCount = totalDurationCentiseconds / gcd; 87 | UIImage *frames[frameCount]; 88 | for (size_t i = 0, f = 0; i < count; ++i) { 89 | UIImage *const frame = [UIImage imageWithCGImage:images[i]]; 90 | for (size_t j = delayCentiseconds[i] / gcd; j > 0; --j) { 91 | frames[f++] = frame; 92 | } 93 | } 94 | return [NSArray arrayWithObjects:frames count:frameCount]; 95 | } 96 | 97 | static void releaseImages(size_t const count, CGImageRef const images[count]) { 98 | for (size_t i = 0; i < count; ++i) { 99 | CGImageRelease(images[i]); 100 | } 101 | } 102 | 103 | static UIImage *animatedImageWithAnimatedGIFImageSource(CGImageSourceRef const source) { 104 | size_t const count = CGImageSourceGetCount(source); 105 | CGImageRef images[count]; 106 | int delayCentiseconds[count]; // in centiseconds 107 | createImagesAndDelays(source, count, images, delayCentiseconds); 108 | int const totalDurationCentiseconds = sum(count, delayCentiseconds); 109 | NSArray *const frames = frameArray(count, images, delayCentiseconds, totalDurationCentiseconds); 110 | UIImage *const animation = [UIImage animatedImageWithImages:frames duration:(NSTimeInterval)totalDurationCentiseconds / 100.0]; 111 | releaseImages(count, images); 112 | return animation; 113 | } 114 | 115 | static UIImage *animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceRef CF_RELEASES_ARGUMENT source) { 116 | if (source) { 117 | UIImage *const image = animatedImageWithAnimatedGIFImageSource(source); 118 | CFRelease(source); 119 | return image; 120 | } 121 | else { 122 | return nil; 123 | } 124 | } 125 | 126 | + (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)data { 127 | return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithData(toCF data, NULL)); 128 | } 129 | 130 | + (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url { 131 | return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithURL(toCF url, NULL)); 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /Source/Controllers/DBInteractiveKeyboardController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBInteractiveKeyboardController.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-05. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @class DBInteractiveKeyboardController; 18 | 19 | /** 20 | * Posted when the system keyboard frame changes. 21 | */ 22 | extern NSString * const DBInteractiveKeyboardDidChangeFrameNotification; 23 | 24 | /** 25 | * Contains the new keyboard frame wrapped in an 'NSValue' object. 26 | */ 27 | extern NSString * const DBInteractiveKeyboardDidChangeFrameUserInfoKey; 28 | 29 | /** 30 | * The 'DBInteractiveKeyboardControllerDelegate' protocol defines methods that 31 | * allow you to respond to the frame change events of the system keyboard. 32 | * 33 | * A 'DBInteractiveKeyboardController' object also posts the 'DBInteractiveKeyboardControllerKeyboardDidChangeFrameNotification' 34 | * in response to frame change events of the system keyboard. 35 | */ 36 | @protocol DBInteractiveKeyboardControllerDelegate 37 | 38 | @required 39 | 40 | /** 41 | * Tells the delegate that the keyboard frame has changed. 42 | * 43 | * @param keyboardController The keyboard controller that is notifying the delegate. 44 | * @param keyboardFrame The new frame of the keyboard in the coordinate system of the 'contextView'. 45 | */ 46 | - (void)keyboardController:(DBInteractiveKeyboardController *)keyboardController keyboardDidChangeFrame:(CGRect)keyboardFrame; 47 | 48 | @optional 49 | 50 | /** 51 | * Tells the delegate that the keyboard will appear. 52 | */ 53 | - (void)keyboardControllerWillAppear:(DBInteractiveKeyboardController *)keyboardController; 54 | 55 | /** 56 | * Tells the delegate that the keyboard did appear. 57 | */ 58 | - (void)keyboardControllerDidAppear:(DBInteractiveKeyboardController *)keyboardController; 59 | 60 | @end 61 | 62 | /** 63 | * An instance of 'DBInteractiveKeyboardController' manages responding to the hiding and showing 64 | * of the system keyboard for editing its 'textView' within its specified 'contextView'. 65 | * It also controls user interaction with the system keyboard via its 'panGestureRecognizer', 66 | * allow the user to interactively pan the keyboard up and down in the 'contextView'. 67 | * 68 | * When the system keyboard frame changes, it posts the 'DBInteractiveKeyboardControllerKeyboardDidChangeFrameNotification'. 69 | */ 70 | @interface DBInteractiveKeyboardController : NSObject 71 | 72 | /** 73 | * The object that acts as the delegate of the keyboard controller. 74 | */ 75 | @property (weak, nonatomic) id delegate; 76 | 77 | /** 78 | * The text view in which the user is editing with the system keyboard. 79 | */ 80 | @property (weak, nonatomic) UITextView *textView; 81 | 82 | /** 83 | * The view in which the keyboard will be shown. This should be the parent or a sibling of `textView`. 84 | */ 85 | @property (weak, nonatomic) UIView *contextView; 86 | 87 | /** 88 | * The view in which the keyboard will be shown. This should be the parent or a sibling of `textView`. 89 | */ 90 | @property (weak, nonatomic) UIPanGestureRecognizer *panGestureRecognizer; 91 | 92 | /** 93 | * Returns 'YES' if the keyboard is currently visible, 'NO' otherwise. 94 | */ 95 | @property (assign, nonatomic) BOOL keyboardIsVisible; 96 | 97 | /** 98 | * Specifies the distance from the keyboard at which the 'panGestureRecognizer' 99 | * should trigger user interaction with the keyboard by panning. 100 | * 101 | * @discussion The x value of the point is not used. 102 | */ 103 | @property (assign, nonatomic) CGPoint keyboardTriggerPoint; 104 | 105 | /** 106 | * Returns the current frame of the keyboard if it is visible, otherwise `CGRectNull`. 107 | */ 108 | @property (assign, nonatomic) CGRect currentKeyboardFrame; 109 | 110 | /** 111 | * Creates a new keyboard controller object with the specified textView, contextView, panGestureRecognizer, and delegate. 112 | * 113 | * @param textView The text view in which the user is editing with the system keyboard. This value must not be `nil`. 114 | * @param contextView The view in which the keyboard will be shown. This should be the parent or a sibling of `textView`. This 115 | * value must not be 'nil'. 116 | * @param panGestureRecognizer The pan gesture recognizer responsible for handling user interaction with the system keyboard. 117 | * This value must not be 'nil'. 118 | * @param delegate The object that acts as the delegate of the keyboard controller. 119 | * 120 | * @return An initialized 'DBInteractiveKeyboardController' 121 | */ 122 | - (instancetype)initWithTextView:(UITextView *)textView 123 | contextView:(UIView *)contextView 124 | panGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer 125 | delegate:(id)delegate; 126 | 127 | /** 128 | * Tells the keyboard controller that it should begin listening for system keyboard notifications. 129 | */ 130 | - (void)beginListeningForKeyboard; 131 | 132 | /** 133 | * Tells the keyboard controller that it should end listening for system keyboard notifications. 134 | */ 135 | - (void)endListeningForKeyboard; 136 | 137 | @end 138 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewBaseFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewBaseFlowLayout.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-19. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @class DBMessagingCollectionView; 18 | 19 | extern NSString * const DBMessagingCollectionElementKindTimestamp; 20 | 21 | /*! 22 | * The 'DBMessagingCollectionViewBaseFlowLayout' organizes message items in a vertical list. 23 | * Each 'DBMessagingParentCell' in the layout can display messages of arbitrary sizes and avatar images, 24 | * as well as metadata such as a timestamp and sender. You can easily customize the layout via its properties. 25 | * 26 | * @see DBMessagingParentCell 27 | */ 28 | @interface DBMessagingCollectionViewBaseFlowLayout : UICollectionViewFlowLayout 29 | 30 | - (void)commonInit; 31 | 32 | /** 33 | * The collection view object currently using this layout object. 34 | */ 35 | @property (strong, nonatomic, readonly) DBMessagingCollectionView *collectionView; 36 | 37 | /** 38 | * Specifies whether or not the layout should enable spring behavior dynamics. 39 | * 40 | * @discussion The default value is 'NO', which disables "springy" or "bouncy" items in the layout. 41 | * Set to 'YES' if you want items to have spring behavior dynamics similar to iMessage. 42 | * 43 | * @warning Though this feature is mostly stable, it is still considered an experimental feature. 44 | */ 45 | @property (assign, nonatomic) BOOL dynamicsEnabled; 46 | 47 | /** 48 | * Specifies the degree of resistence for the 'springiness' of items in the layout. 49 | * This property has no effect if 'dynamicsEnabled' is set to 'NO'. 50 | * 51 | * @discussion The default value is '1000'. Increasing this value increases the resistance, that is, items become less 52 | * 'bouncy'. 53 | */ 54 | @property (assign, nonatomic) CGFloat springResistanceFactor; 55 | 56 | /** 57 | * Returns the width of items in the layout. 58 | */ 59 | @property (assign, nonatomic, readonly) CGFloat itemWidth; 60 | 61 | /** 62 | * Specifies the padding that should be applied to the 'cellTopLabel'. 63 | * 64 | * @discussion The 'cellToplabel' height is calculated using the boundingBox of the attributed string passed 65 | * by the appropriate dataSource method. 66 | */ 67 | @property (assign, nonatomic) CGFloat cellTopLabelPadding; 68 | 69 | /** 70 | * Specifies the padding that should be applied to the 'messageTopLabel'. 71 | * 72 | * @discussion The 'messageTopLabel' height is calculated using the boundingBox of the attributed string passed 73 | * by the appropriate dataSource method. 74 | */ 75 | @property (assign, nonatomic) CGFloat messageTopLabelPadding; 76 | 77 | /** 78 | * Specifies the padding that should be applied to the 'cellBottomLabel'. 79 | * 80 | * @discussion The 'cellBottomlabel' height is calculated using the boundingBox of the attributed string passed 81 | * by the appropriate dataSource method. 82 | */ 83 | @property (assign, nonatomic) CGFloat cellBottomLabelPadding; 84 | 85 | /** 86 | * The interitem spacing between an incoming and outgoing message. 87 | * 88 | * @discussion The minimumLineSpacing property of a 'UICollectionViewFlowLayout' handles spacing between 89 | * consecutive messages by the same sentByUserID, while the inOutMessageBubbleInteritemSpacing will handle the spacing 90 | * between an incoming and outgoing message which is typically larger. The default is '5.0'. 91 | */ 92 | @property (assign, nonatomic) CGFloat inOutMessageBubbleInteritemSpacing; 93 | 94 | @property (assign, nonatomic) CGSize mediaViewReferenceSize; 95 | 96 | /** 97 | * The following attibutes can be set to customize the appearance of the layout. 98 | * 99 | * @discussion The following attributes correspond to the attributes in a 'MessagingCollectionViewLayoutAttributes' 100 | * instance. 101 | * 102 | * @see 'MessagingCollectionViewLayoutAttributes' 103 | */ 104 | @property (strong, nonatomic) UIFont *messageBubbleFont; 105 | @property (assign, nonatomic) UIEdgeInsets messageBubbleTextViewTextContainerInsets; 106 | @property (assign, nonatomic) CGSize incomingAvatarViewSize; 107 | @property (assign, nonatomic) CGSize outgoingAvatarViewSize; 108 | @property (assign, nonatomic) CGFloat messageBubbleLeftRightMargin; 109 | @property (assign, nonatomic) CGFloat incomingMessageBubbleAvatarSpacing; 110 | @property (assign, nonatomic) CGFloat outgoingMessageBubbleAvatarSpacing; 111 | 112 | /** 113 | * Computes and returns the size of the item specified by indexPath. 114 | * 115 | * @param indexPath The index path of the item to be displayed. 116 | * 117 | * @return The size of the item displayed at indexPath. 118 | */ 119 | - (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath; 120 | 121 | /** 122 | * Returns true if the message at a given index path is an outgoing message or false 123 | * if the message is an incoming message. 124 | * 125 | * @param indexPath The index path of the item. 126 | * 127 | * @return Returns true if the message is an outgoing message or false otherwise. 128 | */ 129 | - (BOOL)isOutgoingMessageAtIndexPath:(NSIndexPath *)indexPath; 130 | 131 | - (CGSize)avatarSizeForIndexPath:(NSIndexPath *)indexPath; 132 | 133 | - (CGFloat)messageBubbleAvatarSpacingForIndexPath:(NSIndexPath *)indexPath; 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DBMessagingKit 2 | 3 | ![alt tag](https://cloud.githubusercontent.com/assets/5367914/5310054/ceb41222-7bfa-11e4-858e-2c6a7fe4c055.gif) 4 |                   5 | ![alt tag](https://cloud.githubusercontent.com/assets/5367914/6097248/707975a4-af84-11e4-989e-a19cb0ca4708.gif) 6 | 7 | *DBMessagingTimestampStyleHidden                  DBMessagingTimestampStyleSliding* 8 | 9 | 10 | #### What is this repository for? 11 | 12 | An open-source Messaging UI Kit for iOS, built with simplicity and customization in mind. The kit provides the tools to create a messaging interface while allowing it to work with your app's schema. This library was built to provide the tools you need to build a messaging UI without the need to force your data into any model object or protocol. 13 | 14 | #### Supports 15 | Supports the following MIME types (Internet Media Type): 16 | - Text 17 | - Image 18 | - Video (In development) 19 | - Location (In development) 20 | 21 | Supports iOS 7.0+, Portrait/Landscape iPhone/iPad 22 | 23 | ####Features#### 24 | - Multiple timestamp styles 25 | - Interactive Keyboard Dismiss 26 | - Customize cell labels 27 | - Customize toolbar buttons 28 | - Arbitrary message sizes 29 | - Data detectors 30 | - Dynamic input text view resizing 31 | - Timestamp formatting 32 | 33 | #### How do I get set up? 34 | ##### View Controller 35 | - Subclass ```DBMessagingViewController``` 36 | - Implement the required and optional methods in ```DBMessagingCollectionViewDataSource``` 37 | - Implement the optional methods in ```DBMessagingCollectionViewDelegateFlowLayout``` 38 | - Set the 'timestampStyle' of your view controller subclass to ```DBMessagingTimestampStyleNone```, ```DBMessagingTimestampStyleHidden```, or ```DBMessagingTimestampStyleSliding```. 39 | 40 | ##### Message Bubbles 41 | - For complex message bubble layouts (such as Facebook Messenger) you can use a ```DBMessageBubbleController``` to figure out for you which message bubble should be displayed for a given message. 42 | - Message bubbles can be created by passing 'template' images to an instance of ```DBMessageBubbleController```. 43 | - You can optionally return a message bubble (UIImageView) in the appropriate dataSource method of your choice or use the convience method ```messageBubbleForItemAtIndexPath:``` of a ```DBMessageBubbleController```. 44 | 45 | ##### Input Toolbar 46 | 47 | The input toolbar's buttons are totally up to you. It is recommended that you add a 'send' button and set it as the toolbar's send button property. You are in charge of handling each button's 'action'. 48 | 49 | ```objectiveC 50 | UIBarButtonItem *cameraBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"camera_button"] style:UIBarButtonItemStylePlain target:self action:@selector(cameraButtonTapped:)]; 51 | UIBarButtonItem *sendBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Send" style:UIBarButtonItemStyleDone target:self action:@selector(sendButtonTapped:)]; 52 | 53 | [self.messageInputToolbar addItem:cameraBarButtonItem position:DBMessagingInputToolbarItemPositionLeft animated:false]; 54 | [self.messageInputToolbar addItem:sendBarButtonItem position:DBMessagingInputToolbarItemPositionRight animated:false]; 55 | ``` 56 | At any time you can ask the input toolbar for the current 'Message Parts'. A message part is a dictionary that contains a mime type and a value. 57 | 58 | ```objective-C 59 | NSString *mime = part[DBMessagePartMIMEKey]; 60 | id value = part[DBMessagePartValueKey]; 61 | ``` 62 | 63 | For example, when composing a message comprised simply of text, the input toolbar will contain a single message part with a mime of ```[DBMessagingTextCell mimeType]``` and value of type ```NSString```. 64 | 65 | ##### Mime Types 66 | 67 | You can choose the 'mime' type that should correspond to each cell. The 'mime' type is used to decide which 68 | type of view should be used to display the value for a given message. The mime type can be accessed at any 69 | time by calling ```[DBMessagingTextCell mimeType]```. 70 | 71 | Examples of possible mime types and associated values (again these are totally up to your app's schema): 72 | - mime -> 'image/url'       value -> The URL for the remote image or video. 73 | - mime -> 'image/plain'   value -> A base64 encoded string representing an image or video from a socket. 74 | - mime -> 'geo/json'        value -> A JSON string representing a geolocation. 75 | - mime -> 'image/jpeg',   value -> An image retrieved from disk. 76 | 77 | ```objectiveC 78 | [DBMessagingTextCell setMimeType:@"text/plain"]; 79 | [DBMessagingImageMediaCell setMimeType:@"image/jpeg"]; 80 | [DBMessagingVideoMediaCell setMimeType:@"video/mp4"]; 81 | [DBMessagingLocationMediaCell setMimeType:@"geo"]; 82 | ``` 83 | 84 | The mime type you for each type of cell corresponds to the type of view that will be chosen to display your data. For example, if a message at a given index path contains text, you should return the same mime type you assigned to ```DBMessagingTextCell```. 85 | 86 | ```objectiveC 87 | - (NSString *)collectionView:(UICollectionView *)collectionView mimeForMessageAtIndexPath:(NSIndexPath *)indexPath { 88 | Message *message = [_messages objectAtIndex:indexPath.row]; 89 | return message.mime; 90 | } 91 | ``` 92 | 93 | ##### Customization 94 | - The library is well-commented. This should help you configure your view however you like. 95 | 96 | #### Components 97 | 98 | A component is built to handle a common task in a messging interface. Components are completly optional. 99 | 100 | ##### DBMessagingPhotoPickerController 101 | 102 | A 'DBMessagingPhotoPickerController' provides an interface to quickly choose a photo from the user's recent photos. This component was modelled after a similar photo picker found in iMessage. 103 | 104 | The 'DBMessagingPhotoPickerController' is only available in iOS8. 105 | 106 | ![alt tag](https://cloud.githubusercontent.com/assets/5367914/6176598/2ad436ea-b2ce-11e4-9760-8ab1647d174d.png) 107 | -------------------------------------------------------------------------------- /Source/Controllers/DBMessageBubbleController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessageBubbleController.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-14. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @class DBMessagingCollectionView; 18 | 19 | /** 20 | * An instance of 'DBMessageBubbleController' is a convenience object for managing complex message bubble layouts. 21 | * The logic is based on the idea of 'consecutive' message groups in which multiple messages are sent by the 22 | * same sentByUserID consecutively. 23 | * 24 | * A 'DBMessageBubbleController' handles creation of message bubbles using the 'DBMessageBubbleFactory'. 25 | */ 26 | @interface DBMessageBubbleController : NSObject 27 | 28 | /** 29 | * The messaging collection view object currently using this message bubble controller. 30 | */ 31 | @property (weak, nonatomic, readonly) DBMessagingCollectionView *collectionView; 32 | 33 | /** 34 | * Returns the color for incoming message bubbles. 35 | */ 36 | @property (strong, nonatomic) UIColor *incomingMessageBubbleColor; 37 | 38 | /** 39 | * Returns the color for outgoing message bubbles. 40 | */ 41 | @property (strong, nonatomic) UIColor *outgoingMessageBubbleColor; 42 | 43 | /** 44 | * Initializes a 'DBMessageBubbleController' with the default message bubble colors and the passed 'DBMessagingCollectionView' that will utilize the message bubble controller. The 'DBMessagingCollectionView' utilizes the 'DBMessagingCollectionView' instance in order to access the appropriate dataSource and 45 | * delegate methods needed to determine the appropriate message bubbles returned by messageBubbleForIndexPath:. 46 | * 47 | * @param collectionView The messaging collection view that will utilize the 'DBMessageBubbleController'. 48 | * 49 | * @discussion If not specified the default message bubble will be used instead. 50 | * 51 | * @warning The collectionView cannot be 'nil' and must be an instance of a 'DBMessagingCollectionView'. 52 | * 53 | * @see 'DBMessagingCollectionView' 54 | */ 55 | - (instancetype)initWithCollectionView:(DBMessagingCollectionView *)collectionView; 56 | 57 | /** 58 | * Initializes a 'DBMessageBubbleController' with the default message bubble colors and the passed 'DBMessagingCollectionView' that will utilize the message bubble controller. The 'DBMessagingCollectionView' utilizes the 'DBMessagingCollectionView' instance in order to access the appropriate dataSource and 59 | * delegate methods needed to determine the appropriate message bubbles returned by messageBubbleForIndexPath: 60 | * 61 | * @param collectionView The messaging collection view that will utilize the 'DBMessageBubbleController'. 62 | * @param outgoingChatBubbleColor The color to use for outgoing message bubbles. 63 | * @param incomingChatBubbleColor The color to use for incoming message bubbles. 64 | * 65 | * @discussion If not specified the default message bubble will be used instead. 66 | * 67 | * @warning The collectionView cannot be 'nil' and must be an instance of a 'DBMessagingCollectionView'. 68 | * 69 | * @see 'DBMessagingCollectionView' 70 | */ 71 | - (instancetype)initWithCollectionView:(DBMessagingCollectionView *)collectionView outgoingBubbleColor:(UIColor *)outgoingChatBubbleColor incomingBubbleColor:(UIColor *)incomingChatBubbleColor; 72 | 73 | /** 74 | * Creates and configures a message bubble that will be used for the bottom message in a consecutive group 75 | * of messages with the same sentByUserID. 76 | * 77 | * @param templateImage The image to use as a template. 78 | * 79 | * @discussion If not specified the default message bubble will be used instead. 80 | * 81 | * @warning The templateImage must not be 'nil'. 82 | */ 83 | - (void)setBottomTemplateForConsecutiveGroup:(UIImage *)templateImage; 84 | 85 | /** 86 | * Creates and configures a message bubble that will be used for messages in the middle of a consecutive group 87 | * of messages with the same sentByUserID. 88 | * 89 | * @param templateImage The image to use as a template . 90 | * 91 | * @discussion If not specified the default message bubble will be used instead. 92 | * 93 | * @warning The templateImage must not be 'nil'. 94 | */ 95 | - (void)setMiddleTemplateForConsecutiveGroup:(UIImage *)templateImage; 96 | 97 | /** 98 | * Creates and configures a message bubble that will be used for the top message in a consecutive group 99 | * of messages with the same sentByUserID. 100 | * 101 | * @param templateImage The image to use as a template . 102 | * 103 | * @discussion If not specified the default message bubble will be used instead. 104 | * 105 | * @warning The templateImage must not be 'nil'. 106 | */ 107 | - (void)setTopTemplateForConsecutiveGroup:(UIImage *)templateImage; 108 | 109 | /** 110 | * Creates and configures the default message bubble to use in for messages that are not part of a consecutive 111 | * group or where a more specific message bubble has not been specified. 112 | * 113 | * @param templateImage The image to use as a template . 114 | * 115 | * @discussion If not specified the default message bubble will be used instead. 116 | * 117 | * @warning The templateImage must not be 'nil'. The default message bubble must not be 'nil'. 118 | */ 119 | - (void)setDefaultTemplate:(UIImage *)templateImage; 120 | 121 | /** 122 | * Returns the appropriate message bubble for the given indexPath. 123 | * 124 | * @param indexPath The index path that specifies the location of the item. 125 | * 126 | * @return A configured message bubble for the given indexPath. 127 | */ 128 | - (UIImageView *)messageBubbleForIndexPath:(NSIndexPath *)indexPath; 129 | 130 | /** 131 | * Begins a consecutive group at the specified indexPath. 132 | */ 133 | - (void)beginMessageGroupAtIndexPath:(NSIndexPath *)indexPath; 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingCollectionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionView.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-21. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingCollectionView.h" 16 | 17 | #import "DBMessagingCollectionViewDataSource.h" 18 | #import "DBMessagingCollectionViewDelegateFlowLayout.h" 19 | #import "DBMessagingCollectionViewBaseFlowLayout.h" 20 | #import "DBMessagingCollectionViewHiddenTimestampFlowLayout.h" 21 | #import "DBMessagingCollectionViewSlidingTimestampFlowLayout.h" 22 | 23 | #import "DBMessagingImageMediaCell.h" 24 | #import "DBMessagingVideoMediaCell.h" 25 | #import "DBMessagingLocationMediaCell.h" 26 | #import "DBMessagingTimestampSupplementaryView.h" 27 | #import "DBMessagingLoadEarlierMessagesHeaderView.h" 28 | #import "DBMessagingTypingIndicatorFooterView.h" 29 | 30 | @implementation DBMessagingCollectionView 31 | 32 | - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { 33 | self = [super initWithFrame:frame collectionViewLayout:layout]; 34 | if (self) { 35 | [self commonInit]; 36 | } 37 | return self; 38 | } 39 | 40 | - (id)initWithCoder:(NSCoder *)aDecoder { 41 | self = [super initWithCoder:aDecoder]; 42 | if (self) { 43 | [self commonInit]; 44 | } 45 | return self; 46 | } 47 | 48 | - (void)commonInit 49 | { 50 | self.alwaysBounceVertical = YES; 51 | [self setKeyboardDismissMode:UIScrollViewKeyboardDismissModeInteractive]; 52 | 53 | [self registerClass:[DBMessagingTextCell class] forCellWithReuseIdentifier:[DBMessagingTextCell cellReuseIdentifier]]; 54 | [self registerClass:[DBMessagingImageMediaCell class] forCellWithReuseIdentifier:[DBMessagingImageMediaCell cellReuseIdentifier]]; 55 | [self registerClass:[DBMessagingVideoMediaCell class] forCellWithReuseIdentifier:[DBMessagingVideoMediaCell cellReuseIdentifier]]; 56 | [self registerClass:[DBMessagingLocationMediaCell class] forCellWithReuseIdentifier:[DBMessagingLocationMediaCell cellReuseIdentifier]]; 57 | 58 | [self registerClass:[DBMessagingTimestampSupplementaryView class] forSupplementaryViewOfKind:DBMessagingCollectionElementKindTimestamp withReuseIdentifier:[DBMessagingTimestampSupplementaryView viewReuseIdentifier]]; 59 | [self registerClass:[DBMessagingTypingIndicatorFooterView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[DBMessagingTypingIndicatorFooterView viewReuseIdentifier]]; 60 | [self registerClass:[DBMessagingLoadEarlierMessagesHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[DBMessagingLoadEarlierMessagesHeaderView viewReuseIdentifier]]; 61 | } 62 | 63 | - (DBMessagingLoadEarlierMessagesHeaderView *)dequeueLoadEarlierMessagesHeaderViewForIndexPath:(NSIndexPath *)indexPath { 64 | 65 | DBMessagingLoadEarlierMessagesHeaderView *loadMoreHeaderView = 66 | [super dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader 67 | withReuseIdentifier:[DBMessagingLoadEarlierMessagesHeaderView viewReuseIdentifier] 68 | forIndexPath:indexPath]; 69 | 70 | self.loadMoreHeaderView = loadMoreHeaderView; 71 | return loadMoreHeaderView; 72 | } 73 | 74 | - (DBMessagingTypingIndicatorFooterView *)dequeueTypingIndicatorFooterViewForIndexPath:(NSIndexPath *)indexPath { 75 | 76 | DBMessagingTypingIndicatorFooterView *typingIndicatorFooterView = 77 | [super dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter 78 | withReuseIdentifier:[DBMessagingTypingIndicatorFooterView viewReuseIdentifier] 79 | forIndexPath:indexPath]; 80 | 81 | self.typingIndicatorFooterView = typingIndicatorFooterView; 82 | return typingIndicatorFooterView; 83 | } 84 | 85 | - (DBMessagingTimestampSupplementaryView *)dequeueTimestampSupplementaryViewForIndexPath:(NSIndexPath *)indexPath { 86 | 87 | DBMessagingTimestampSupplementaryView *timestampSupplementaryView = 88 | [self dequeueReusableSupplementaryViewOfKind:DBMessagingCollectionElementKindTimestamp 89 | withReuseIdentifier:[DBMessagingTimestampSupplementaryView viewReuseIdentifier] 90 | forIndexPath:indexPath]; 91 | return timestampSupplementaryView; 92 | } 93 | 94 | #pragma mark - DBMessagingParentCellDelegate 95 | 96 | - (void)messageCell:(DBMessagingParentCell *)cell didTapAvatarImageView:(UIImageView *)avatarImageView 97 | { 98 | if ([self.delegate respondsToSelector:@selector(collectionView:didTapAvatarImageView:atIndexPath:)]) { 99 | 100 | [self.delegate collectionView:self 101 | didTapAvatarImageView:avatarImageView 102 | atIndexPath:[self indexPathForCell:cell]]; 103 | } 104 | } 105 | 106 | - (void)messageCell:(DBMessagingParentCell *)cell didTapMediaView:(DBMessagingMediaView *)mediaView 107 | { 108 | if ([self.delegate respondsToSelector:@selector(collectionView:didTapMediaView:atIndexPath:)]) { 109 | 110 | [self.delegate collectionView:self 111 | didTapMediaView:mediaView 112 | atIndexPath:[self indexPathForCell:cell]]; 113 | } 114 | } 115 | 116 | - (void)messageCell:(DBMessagingParentCell *)cell didTapMessageBubbleImageView:(UIImageView *)messageBubbleImageView 117 | { 118 | if ([self.delegate respondsToSelector:@selector(collectionView:didTapMessageBubbleImageView:atIndexPath:)]) { 119 | 120 | [self.delegate collectionView:self 121 | didTapMessageBubbleImageView:messageBubbleImageView 122 | atIndexPath:[self indexPathForCell:cell]]; 123 | } 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /Source/Controllers/DBMessagingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingViewController.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/MessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-12. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | #import "DBMessagingKitConstants.h" 18 | #import "DBMessagingCollectionViewDataSource.h" 19 | #import "DBMessagingCollectionViewDelegate.h" 20 | #import "DBMessagingCollectionViewDelegateFlowLayout.h" 21 | 22 | #import 23 | #import 24 | 25 | @class DBMessagingCollectionView; 26 | @class DBInteractiveKeyboardController; 27 | @class DBMessagingInputToolbar; 28 | 29 | /** 30 | * The 'DBMessagingViewController' class is an abstract class that represents a view controller whose content consists of 31 | * a 'DBMessagingCollectionView' and 'DBMessagingInputToolbar' and is specialized to display a messaging interface. 32 | * 33 | * @warning This class is intended to be subclassed. You should not use it directly. 34 | */ 35 | @interface DBMessagingViewController : UIViewController 36 | 37 | /** 38 | * Returns your device's current location. 39 | * 40 | * @warning You must call [self.locationManager startUpdatingLocation] before trying to access the current location. Note that this 41 | * will ask the user for the appropriate permission that has been configured in the info.plist. 42 | */ 43 | @property (strong, nonatomic, readonly) CLLocation *currentLocation; 44 | 45 | /** 46 | * Returns the location manager used to get your device's current location. 47 | */ 48 | @property (strong, nonatomic, readonly) CLLocationManager *locationManager; 49 | 50 | /** 51 | * Returns the collection view object managed by this view controller. 52 | * This view controller is the collection view's data source and delegate. 53 | */ 54 | @property (strong, nonatomic, readonly) DBMessagingCollectionView *collectionView; 55 | 56 | /** 57 | * The keyboard controller object for the 'DBMessagingViewController' 58 | */ 59 | @property (strong, nonatomic, readonly) DBInteractiveKeyboardController *keyboardController; 60 | 61 | /** 62 | * Returns the messaging input view for the 'DBMessagingViewController' 63 | */ 64 | @property (strong, nonatomic, readonly) DBMessagingInputToolbar *messageInputToolbar; 65 | 66 | /** 67 | * Specifys the type of layout in which to display timestamps. The default value is 'DBMessagingTimestampStyleNone'. 68 | * 69 | * @warning You must specify the timestamp style before configuring any custom attributes. Currently changing the timestamp 70 | * style at runtime is not supported. 71 | */ 72 | @property (assign, nonatomic) DBMessagingTimestampStyle timestampStyle; 73 | 74 | /** 75 | * Specifies whether or not to accept any auto-correct suggestions before sending a message. 76 | * 77 | * @discussion The default value is 'YES'. 78 | */ 79 | @property (nonatomic) BOOL acceptsAutoCorrectBeforeSending; 80 | 81 | /** 82 | * Specifies whether or not the view controller should automatically scroll to the most recent message 83 | * when composing a new message and when sending or receiving a new message. 84 | * 85 | * @discussion The default value is 'YES', which allows the view controller to scroll automatically to the most recent message. 86 | * Set to 'NO' if you want to manage scrolling yourself. 87 | */ 88 | @property (assign, nonatomic) BOOL automaticallyScrollsToMostRecentMessage; 89 | 90 | /** 91 | * Specifies whether or not the view controller should show the "load earlier messages" header view. 92 | * 93 | * @discussion Setting this property to 'YES' will show the header view immediately. 94 | * Settings this property to 'NO' will hide the header view immediately. You will need to scroll to 95 | * the top of the collection view in order to see the header. 96 | */ 97 | @property (nonatomic) BOOL showLoadMoreMessages; 98 | 99 | /** 100 | * Specifies whether or not the view controller should show the typing indicator for an incoming message. 101 | * 102 | * @discussion Setting this property to 'YES' will animate showing the typing indicator immediately. 103 | * Setting this property to 'NO' will animate hiding the typing indicator immediately. You will need to scroll 104 | * to the bottom of the collection view in order to see the typing indicator. You may use 'scrollToBottomAnimated:' for this. 105 | */ 106 | @property (nonatomic) BOOL showTypingIndicator; 107 | 108 | /** 109 | * A convenience method for returning the index path fo the latest message that was sent or recieved. 110 | * 111 | * @return Returns the indexPath for the latest message. 112 | */ 113 | - (NSIndexPath *)indexPathForLatestMessage; 114 | 115 | /** 116 | * Completes the sending of a new message by clearing the currently composed text, 117 | * reloading the collection view, and scrolling to the newly sent message 118 | * as specified by 'automaticallyScrollsToMostRecentMessage'. 119 | * 120 | * @discussion You should call this method after adding a new sent message 121 | * to your data source and performing any related tasks. 122 | * 123 | * @see 'automaticallyScrollsToMostRecentMessage'. 124 | */ 125 | - (void)finishSendingMessage; 126 | 127 | /** 128 | * Completes the receiving of a new message by animating the typing indicator, 129 | * animating the addition of a new collection view cell in the collection view, 130 | * reloading the collection view, and scrolling to the newly received message 131 | * as specified by 'automaticallyScrollsToMostRecentMessage'. 132 | * 133 | * @discussion You should call this method after adding a new received message 134 | * to your data source and performing any related tasks. 135 | * 136 | * @see 'automaticallyScrollsToMostRecentMessage'. 137 | */ 138 | - (void)finishReceivingMessage; 139 | 140 | /** 141 | * Scrolls the collection view such that the bottom most cell is completely visible, above the 'inputToolbar'. 142 | * 143 | * @param animated Pass 'YES' if you want to animate scrolling, 'NO' if it should be immediate. 144 | */ 145 | - (void)scrollToBottomAnimated:(BOOL)animated; 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingInputToolbar.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessageInputView.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-09-19. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingInputToolbar.h" 16 | 17 | #import "DBMessagingInputTextView.h" 18 | #import "UIColor+Messaging.h" 19 | 20 | @interface DBMessagingInputToolbar () 21 | { 22 | 23 | UIBarButtonItem *_flexibleSpace; 24 | } 25 | 26 | @property (strong, nonatomic) CALayer *topBorderLayer; 27 | @property (strong, nonatomic) UIToolbar *blurToolbar; 28 | 29 | @property (strong, nonatomic) DBMessagingInputTextView *textView; 30 | @property (strong, nonatomic) UIToolbar *contentToolbar; 31 | 32 | @end 33 | 34 | @implementation DBMessagingInputToolbar 35 | 36 | - (instancetype)initWithFrame:(CGRect)frame 37 | { 38 | self = [super initWithFrame:frame]; 39 | if (self) { 40 | 41 | _blurToolbar = [[UIToolbar alloc] initWithFrame:self.bounds]; 42 | [_blurToolbar setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; 43 | [self addSubview:_blurToolbar]; 44 | 45 | _contentToolbar = [[UIToolbar alloc] initWithFrame:self.bounds]; 46 | _contentToolbar.translucent = true; 47 | [_contentToolbar setTintColor:[UIColor defaultToolbarTintColor]]; 48 | [_contentToolbar setBarStyle:UIBarStyleBlack]; 49 | [_contentToolbar setBackgroundColor:[UIColor clearColor]]; 50 | [_contentToolbar setBackgroundImage:[[UIImage alloc] init] forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault]; 51 | [_contentToolbar setShadowImage:[[UIImage alloc] init] forToolbarPosition:UIToolbarPositionAny]; 52 | [_contentToolbar setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin]; 53 | [self addSubview:_contentToolbar]; 54 | 55 | _topBorderLayer = [CALayer layer]; 56 | [self.layer addSublayer:_topBorderLayer]; 57 | 58 | _textView = [[DBMessagingInputTextView alloc] initWithFrame:CGRectMake(0,0,0.0,30.0)]; 59 | [_textView setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; 60 | [_textView setDelegate:self]; 61 | [self addSubview:_textView]; 62 | 63 | _blur = YES; 64 | _borderColor = [UIColor lightGrayColor]; 65 | _borderWidth = 1.0; 66 | 67 | _flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; 68 | [_contentToolbar setItems:@[_flexibleSpace]]; 69 | 70 | } 71 | 72 | return self; 73 | } 74 | 75 | - (void)layoutSubviews { 76 | 77 | [super layoutSubviews]; 78 | 79 | NSInteger index = 0; 80 | UIBarButtonItem *innerLeftBarButtonItem; 81 | UIBarButtonItem *innerRightBarButtonItem; 82 | for (UIBarButtonItem *barButtonItem in _contentToolbar.items) { 83 | if (barButtonItem == _flexibleSpace) { 84 | if (index - 1 >= 0) { 85 | innerLeftBarButtonItem = _contentToolbar.items[index - 1]; 86 | } 87 | 88 | if (index + 1 <= _contentToolbar.items.count - 1) { 89 | innerRightBarButtonItem = _contentToolbar.items[index + 1]; 90 | } 91 | } 92 | index++; 93 | } 94 | 95 | UIEdgeInsets insets = UIEdgeInsetsMake(0, 15.0, 0, 15.0); 96 | CGFloat margin = 8.0; 97 | 98 | if (innerLeftBarButtonItem) { 99 | UIView *innerLeftView = [innerLeftBarButtonItem valueForKey:@"view"]; 100 | insets.left = CGRectGetMaxX(innerLeftView.frame) + margin; 101 | } 102 | 103 | if (innerRightBarButtonItem) { 104 | UIView *innerRightView = [innerRightBarButtonItem valueForKey:@"view"]; 105 | insets.right = self.bounds.size.width - CGRectGetMinX(innerRightView.frame) + margin; 106 | } 107 | 108 | _textView.frame = CGRectMake(insets.left, 0, self.bounds.size.width - insets.left - insets.right, _textView.frame.size.height); 109 | _textView.center = CGPointMake(_textView.center.x, self.bounds.size.height / 2.0); 110 | 111 | [_topBorderLayer setFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), _borderWidth)]; 112 | } 113 | 114 | #pragma mark - Setters 115 | 116 | - (void)setBlur:(BOOL)blur 117 | { 118 | _blur = blur; 119 | 120 | if (blur) { 121 | [_topBorderLayer setOpacity:0.0]; 122 | [_contentToolbar setAlpha:1.0]; 123 | [self setBackgroundColor:[UIColor clearColor]]; 124 | } 125 | else { 126 | [_topBorderLayer setOpacity:1.0]; 127 | [_contentToolbar setAlpha:0.0]; 128 | [self setBackgroundColor:[UIColor whiteColor]]; 129 | } 130 | } 131 | 132 | - (void)setBorderWidth:(NSInteger)borderWidth 133 | { 134 | _borderWidth = borderWidth; 135 | [self setNeedsLayout]; 136 | } 137 | 138 | - (void)setBorderColor:(UIColor *)borderColor 139 | { 140 | _borderColor = borderColor; 141 | [_topBorderLayer setBackgroundColor:borderColor.CGColor]; 142 | } 143 | 144 | - (void)setSendBarButtonItem:(UIBarButtonItem *)sendBarButtonItem { 145 | 146 | _sendBarButtonItem = sendBarButtonItem; 147 | [self toggleSendButtonEnabled]; 148 | } 149 | 150 | - (void)addItem:(UIBarButtonItem *)item position:(DBMessagingInputToolbarItemPosition)position animated:(BOOL)animated { 151 | 152 | NSMutableArray *mutableItems = [[NSMutableArray alloc] initWithArray:_contentToolbar.items]; 153 | 154 | switch (position) { 155 | case DBMessagingInputToolbarItemPositionLeft: 156 | [mutableItems insertObject:item atIndex:0]; 157 | break; 158 | case DBMessagingInputToolbarItemPositionRight: 159 | [mutableItems addObject:item]; 160 | break; 161 | default: 162 | break; 163 | } 164 | 165 | [_contentToolbar setItems:mutableItems animated:animated]; 166 | 167 | [self setNeedsLayout]; 168 | } 169 | 170 | #pragma mark - Actions 171 | 172 | - (void)toggleSendButtonEnabled 173 | { 174 | if (_sendBarButtonItem) { 175 | _sendBarButtonItem.enabled = [_textView.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length > 0; 176 | } 177 | } 178 | 179 | #pragma mark - DBMessagingInputTextViewDelegate 180 | 181 | - (void)textViewDidChange:(UITextView *)textView { 182 | [self toggleSendButtonEnabled]; 183 | } 184 | 185 | - (void)textViewDidBeginEditing:(UITextView *)textView { 186 | [self.delegate messagingInputToolbarDidBeginEditing:self]; 187 | } 188 | 189 | - (void)textViewDidChangeFrame:(UITextView *)textView delta:(CGFloat)delta { 190 | [self.delegate messagingInputToolbar:self shouldChangeFrame:delta]; 191 | } 192 | 193 | - (BOOL)textView:(DBMessagingInputTextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { 194 | 195 | if ([text isEqualToString:@""]) { 196 | 197 | NSValue *rangeValue = [NSValue valueWithRange:range]; 198 | 199 | if ([textView.attatchmentRanges containsObject:rangeValue]) { 200 | [textView removeAttatchmentAtRange:range]; 201 | } 202 | } 203 | 204 | return true; 205 | } 206 | 207 | @end 208 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewHiddenTimestampFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewHiddenTimestampFlowLayout.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-08. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingCollectionViewHiddenTimestampFlowLayout.h" 16 | 17 | #import "DBMessagingCollectionViewLayoutAttributes.h" 18 | #import "DBMessagingCollectionViewFlowLayoutInvalidationContext.h" 19 | #import "DBMessagingCollectionView.h" 20 | #import "NSAttributedString+Messaging.h" 21 | 22 | @interface DBMessagingCollectionViewHiddenTimestampFlowLayout () 23 | 24 | @end 25 | 26 | @implementation DBMessagingCollectionViewHiddenTimestampFlowLayout 27 | 28 | - (void)prepareLayout { 29 | [super prepareLayout]; 30 | 31 | _timestampSupplementaryViewPadding = 10.0f; 32 | } 33 | 34 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 35 | NSArray *superAttrributes = [super layoutAttributesForElementsInRect:rect]; 36 | NSMutableArray *attributesInRect = [superAttrributes mutableCopy]; 37 | 38 | // Add supplementary views to specfic index paths 39 | for (UICollectionViewLayoutAttributes *attributes in superAttrributes) 40 | { 41 | [attributesInRect addObject:[self layoutAttributesForSupplementaryViewOfKind:DBMessagingCollectionElementKindTimestamp atIndexPath:attributes.indexPath]]; 42 | } 43 | 44 | [attributesInRect enumerateObjectsUsingBlock:^(DBMessagingCollectionViewLayoutAttributes *layoutAttributes, NSUInteger idx, BOOL *stop) { 45 | 46 | if (_tappedIndexPath) { 47 | if ([_tappedIndexPath compare:layoutAttributes.indexPath] == NSOrderedAscending) { 48 | layoutAttributes.frame = [self _adjustedFrameForAttributes:layoutAttributes forElementKind:DBMessagingCollectionElementKindTimestamp]; 49 | } 50 | } 51 | }]; 52 | 53 | 54 | return attributesInRect; 55 | } 56 | 57 | - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath 58 | { 59 | DBMessagingCollectionViewLayoutAttributes *layoutAttributes = [DBMessagingCollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath]; 60 | 61 | if (layoutAttributes) { 62 | 63 | //get the attributes for the related cell at this index path 64 | UICollectionViewLayoutAttributes *cellAttributes = [self layoutAttributesForItemAtIndexPath:indexPath]; 65 | 66 | if ([elementKind isEqualToString:DBMessagingCollectionElementKindTimestamp]) { 67 | layoutAttributes.incomingAvatarViewSize = self.incomingAvatarViewSize; 68 | layoutAttributes.outgoingAvatarViewSize = self.outgoingAvatarViewSize; 69 | layoutAttributes.messageBubbleTextViewTextContainerInsets = self.messageBubbleTextViewTextContainerInsets; 70 | 71 | if ([indexPath isEqual:_tappedIndexPath]) { 72 | layoutAttributes.frame = CGRectMake(CGRectGetMinX(cellAttributes.frame), CGRectGetMaxY(cellAttributes.frame), self.itemWidth, [self _timestampSupplementaryViewHeightForIndexPath:indexPath]); 73 | } 74 | else { 75 | layoutAttributes.frame = CGRectZero; 76 | } 77 | } 78 | } 79 | 80 | return layoutAttributes; 81 | } 82 | 83 | #pragma mark - Setters 84 | 85 | - (void)setTimestampSupplementaryViewPadding:(CGFloat)timestampSupplementaryViewPadding 86 | { 87 | _timestampSupplementaryViewPadding = timestampSupplementaryViewPadding; 88 | [self invalidateLayoutWithContext:[DBMessagingCollectionViewFlowLayoutInvalidationContext context]]; 89 | } 90 | 91 | - (void)setTappedIndexPath:(NSIndexPath *)tappedIndexPath 92 | { 93 | _tappedIndexPath = ([_tappedIndexPath isEqual:tappedIndexPath]) ? nil : tappedIndexPath; 94 | 95 | // Highlight the selected item 96 | [self.collectionView selectItemAtIndexPath:_tappedIndexPath 97 | animated:YES 98 | scrollPosition:UICollectionViewScrollPositionNone]; 99 | 100 | // Animate the timestamp to become visible 101 | [self.collectionView performBatchUpdates:^{ 102 | 103 | if (_tappedIndexPath) { 104 | // Scroll to make the timestamp visible 105 | CGRect visibleRect = [self.collectionView cellForItemAtIndexPath:_tappedIndexPath].frame; 106 | visibleRect.origin.y += [self _timestampSupplementaryViewHeightForIndexPath:_tappedIndexPath]; 107 | [self.collectionView scrollRectToVisible:visibleRect animated:true]; 108 | } 109 | 110 | } completion:^(BOOL finished) { 111 | if (finished) { 112 | [self invalidateLayout]; 113 | } 114 | }]; 115 | } 116 | 117 | #pragma mark - Getters 118 | 119 | - (CGSize)collectionViewContentSize 120 | { 121 | CGSize contentSize = [super collectionViewContentSize]; 122 | 123 | if (self.tappedIndexPath) { 124 | contentSize.height += [self _timestampSupplementaryViewHeightForIndexPath:self.tappedIndexPath]; 125 | } 126 | 127 | return contentSize; 128 | } 129 | 130 | - (CGFloat)itemWidth 131 | { 132 | return CGRectGetWidth(self.collectionView.bounds) - self.sectionInset.left - self.sectionInset.right; 133 | } 134 | 135 | #pragma mark - Utility 136 | 137 | - (CGRect)_adjustedFrameForAttributes:(UICollectionViewLayoutAttributes *)attributes forElementKind:(NSString *)elementKind 138 | { 139 | CGRect frame = attributes.frame; 140 | if ([elementKind isEqualToString:DBMessagingCollectionElementKindTimestamp]) { 141 | frame.origin.y += [self _timestampSupplementaryViewHeightForIndexPath:attributes.indexPath]; 142 | } 143 | 144 | return frame; 145 | } 146 | 147 | - (CGFloat)_timestampSupplementaryViewHeightForIndexPath:(NSIndexPath *)indexPath 148 | { 149 | CGFloat timestampSupplementaryViewHeight = 0; 150 | if ([self.collectionView.dataSource respondsToSelector:@selector(collectionView:cellBottomLabelAttributedTextForItemAtIndexPath:)]) { 151 | NSAttributedString *timestampAttributedString = [self.collectionView.dataSource collectionView:self.collectionView timestampAttributedTextForSupplementaryViewAtIndexPath:indexPath]; 152 | timestampSupplementaryViewHeight = [NSAttributedString boundingBoxForAttributedString:timestampAttributedString maxWidth:self.itemWidth].height; 153 | 154 | if (timestampSupplementaryViewHeight > 0) { 155 | timestampSupplementaryViewHeight += self.timestampSupplementaryViewPadding; 156 | } 157 | } 158 | 159 | return timestampSupplementaryViewHeight; 160 | } 161 | 162 | - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath { 163 | 164 | UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:elementIndexPath]; 165 | 166 | if ([elementKind isEqualToString:DBMessagingCollectionElementKindTimestamp]) { 167 | CGAffineTransform translation = CGAffineTransformMakeTranslation(0, 0); 168 | CGFloat translationInset = [self messageBubbleAvatarSpacingForIndexPath:elementIndexPath] + [self avatarSizeForIndexPath:elementIndexPath].width + 50.0; 169 | 170 | if ([self isOutgoingMessageAtIndexPath:elementIndexPath]) { 171 | translation = CGAffineTransformMakeTranslation((layoutAttributes.frame.size.width - translationInset - self.sectionInset.right), -layoutAttributes.frame.size.height); 172 | } 173 | else { 174 | translation = CGAffineTransformMakeTranslation(-(layoutAttributes.frame.size.width - translationInset - self.sectionInset.left), -layoutAttributes.frame.size.height); 175 | } 176 | 177 | layoutAttributes.transform = CGAffineTransformScale(translation, 0.0, 0.0); 178 | } 179 | 180 | return layoutAttributes; 181 | } 182 | 183 | @end 184 | -------------------------------------------------------------------------------- /Source/Layout/DBMessagingCollectionViewSlidingTimestampFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewSlidingTimestampFlowLayout.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2015-02-08. 10 | // Copyright (c) 2015 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingCollectionViewSlidingTimestampFlowLayout.h" 16 | 17 | #import "DBMessagingCollectionViewLayoutAttributes.h" 18 | #import "DBMessagingCollectionViewFlowLayoutInvalidationContext.h" 19 | #import "DBMessagingCollectionView.h" 20 | #import "NSAttributedString+Messaging.h" 21 | 22 | @interface DBMessagingCollectionViewSlidingTimestampFlowLayout () 23 | 24 | @end 25 | 26 | @implementation DBMessagingCollectionViewSlidingTimestampFlowLayout 27 | 28 | - (instancetype)init 29 | { 30 | self = [super init]; 31 | if (self) { 32 | [self commonInit]; 33 | } 34 | return self; 35 | } 36 | 37 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 38 | self = [super initWithCoder:aDecoder]; 39 | if (self){ 40 | [self commonInit]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)commonInit { 46 | [super commonInit]; 47 | 48 | // Add an aditional pan gesture to the collection view 49 | _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; 50 | _panGesture.delegate = self; 51 | } 52 | 53 | - (void)prepareLayout { 54 | [super prepareLayout]; 55 | 56 | [self.collectionView addGestureRecognizer:_panGesture]; 57 | } 58 | 59 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 60 | NSArray *superAttrributes = [super layoutAttributesForElementsInRect:rect]; 61 | NSMutableArray *attributesInRect = [superAttrributes mutableCopy]; 62 | 63 | for (UICollectionViewLayoutAttributes *attributes in superAttrributes) { 64 | [attributesInRect addObject:[self layoutAttributesForSupplementaryViewOfKind:DBMessagingCollectionElementKindTimestamp atIndexPath:attributes.indexPath]]; 65 | } 66 | 67 | // Move the cells 68 | if (_panning) { 69 | [attributesInRect enumerateObjectsUsingBlock:^(DBMessagingCollectionViewLayoutAttributes *layoutAttributes, NSUInteger idx, BOOL *stop) { 70 | 71 | CGFloat change = _startLocation.x - _panLocation.x; 72 | CGFloat maxChange = self.messageBubbleLeftRightMargin - self.sectionInset.left; 73 | CGRect frame = layoutAttributes.frame; 74 | 75 | change /= 2.0; 76 | 77 | if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) { 78 | if ([self isOutgoingMessageAtIndexPath:layoutAttributes.indexPath]) { 79 | if (change <= maxChange) { 80 | frame.origin.x = MIN(-change + self.sectionInset.left, self.sectionInset.left); 81 | } else { 82 | frame.origin.x = -maxChange + self.sectionInset.left; 83 | } 84 | 85 | } else if (!CGSizeEqualToSize([self avatarSizeForIndexPath:layoutAttributes.indexPath], CGSizeZero)){ 86 | // If incoming avatar's size is greater than zero, they also slide just enough to hide the avatars. 87 | 88 | change /= (maxChange / ([self avatarSizeForIndexPath:layoutAttributes.indexPath].width + self.incomingMessageBubbleAvatarSpacing)); 89 | maxChange = [self avatarSizeForIndexPath:layoutAttributes.indexPath].width + self.incomingMessageBubbleAvatarSpacing; 90 | 91 | if (change <= maxChange) { 92 | frame.origin.x = MIN(-change + self.sectionInset.left, self.sectionInset.left); 93 | } else { 94 | frame.origin.x = -maxChange + self.sectionInset.left; 95 | } 96 | 97 | change /= (maxChange / abs(self.sectionInset.left - self.incomingMessageBubbleAvatarSpacing)); 98 | maxChange = [self avatarSizeForIndexPath:layoutAttributes.indexPath].width + self.sectionInset.left; 99 | layoutAttributes.slidingTimestampAvatarDistance = MIN(change, maxChange); 100 | } 101 | 102 | layoutAttributes.slidingTimestampDistance = MAX(MIN(change, maxChange), 0); 103 | 104 | } else if (layoutAttributes.representedElementCategory == UICollectionElementCategorySupplementaryView) { 105 | if (layoutAttributes.representedElementKind == DBMessagingCollectionElementKindTimestamp) { 106 | CGFloat max = self.collectionView.frame.size.width; 107 | CGFloat relativeWidth = self.collectionView.frame.size.width - self.sectionInset.right; 108 | if (change < maxChange) { 109 | frame.origin.x = MIN(relativeWidth - change, max); 110 | } else { 111 | frame.origin.x = MIN(relativeWidth - maxChange, max); 112 | } 113 | } 114 | } 115 | 116 | layoutAttributes.frame = frame; 117 | }]; 118 | } 119 | 120 | return attributesInRect; 121 | } 122 | 123 | - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath 124 | { 125 | DBMessagingCollectionViewLayoutAttributes *layoutAttributes = [DBMessagingCollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath]; 126 | 127 | if (layoutAttributes) { 128 | 129 | DBMessagingCollectionViewLayoutAttributes *cellAttributes = (DBMessagingCollectionViewLayoutAttributes *)[self layoutAttributesForItemAtIndexPath:indexPath]; 130 | CGRect cellFrame = cellAttributes.frame; 131 | 132 | if ([elementKind isEqualToString:DBMessagingCollectionElementKindTimestamp]) { 133 | layoutAttributes.frame = CGRectMake(self.collectionView.bounds.size.width, cellFrame.origin.y, self.messageBubbleLeftRightMargin, cellFrame.size.height); 134 | layoutAttributes.cellTopLabelHeight = cellAttributes.cellTopLabelHeight; 135 | layoutAttributes.messageBubbleTopLabelHeight = cellAttributes.messageBubbleTopLabelHeight; 136 | } 137 | } 138 | 139 | return layoutAttributes; 140 | } 141 | 142 | #pragma mark - Gestures 143 | 144 | - (void)handlePan:(UIPanGestureRecognizer *)panGesture { 145 | 146 | CGPoint velocity = [panGesture velocityInView:panGesture.view]; 147 | CGPoint cvVelocity = [self.collectionView.panGestureRecognizer velocityInView:panGesture.view]; 148 | 149 | if (panGesture.state == UIGestureRecognizerStateBegan) { 150 | 151 | if (velocity.x < 0 && abs(cvVelocity.y) < 30.0) { 152 | _startLocation = [panGesture locationInView:self.collectionView]; 153 | _panning = true; 154 | } 155 | } 156 | 157 | if (panGesture.state == UIGestureRecognizerStateChanged) { 158 | CGPoint panLocation = [panGesture locationInView:self.collectionView]; 159 | 160 | if (_panning && panLocation.x != _panLocation.x) { 161 | _panLocation = panLocation; 162 | [self invalidateLayoutWithContext:[DBMessagingCollectionViewFlowLayoutInvalidationContext context]]; 163 | } 164 | } 165 | 166 | if (panGesture.state == UIGestureRecognizerStateEnded || 167 | panGesture.state == UIGestureRecognizerStateCancelled) { 168 | 169 | if (_panning) { 170 | _panning = false; 171 | [self.collectionView performBatchUpdates:nil completion:nil]; 172 | } 173 | } 174 | } 175 | 176 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 177 | 178 | return otherGestureRecognizer == self.collectionView.panGestureRecognizer; 179 | } 180 | 181 | @end 182 | -------------------------------------------------------------------------------- /Source/Views/DBMessagingTextCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingTextCell.m 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-10. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import "DBMessagingTextCell.h" 16 | 17 | #import "UIColor+Messaging.h" 18 | 19 | #import "DBMessagingCellTextView.h" 20 | #import "DBMessagingCollectionView.h" 21 | #import "DBMessagingCollectionViewHiddenTimestampFlowLayout.h" 22 | #import "DBMessagingCollectionViewLayoutAttributes.h" 23 | 24 | static NSString *kDBMessagingTextCellMimeType = @"text/plain"; 25 | 26 | @interface DBMessagingTextCell () 27 | 28 | @property (strong, nonatomic) DBMessagingCellTextView *messageTextView; 29 | 30 | @property (strong, nonatomic) UILongPressGestureRecognizer *longPress; 31 | @property (strong, nonatomic) UITapGestureRecognizer *messageTextViewTap; 32 | 33 | @end 34 | 35 | @implementation DBMessagingTextCell 36 | 37 | + (NSString *)mimeType { 38 | return kDBMessagingTextCellMimeType; 39 | } 40 | 41 | + (void)setMimeType:(NSString *)mimeType { 42 | NSAssert(![mimeType isEqualToString:@""] || mimeType != nil, @"Mime type for class %@ cannot be nil.", [self class]); 43 | kDBMessagingTextCellMimeType = mimeType; 44 | } 45 | 46 | + (NSString *)cellReuseIdentifier { 47 | return NSStringFromClass([self class]); 48 | } 49 | 50 | - (instancetype)initWithFrame:(CGRect)frame 51 | { 52 | self = [super initWithFrame:frame]; 53 | if (self) { 54 | _messageTextView = [[DBMessagingCellTextView alloc] init]; 55 | [_messageTextView setDelegate:self]; 56 | [_messageTextView setFrame:self.messageBubbleImageView.frame]; 57 | [_messageTextView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; 58 | [self.messageBubbleImageView addSubview:self.messageTextView]; 59 | 60 | _longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)]; 61 | _longPress.minimumPressDuration = 0.5f; 62 | [self addGestureRecognizer:_longPress]; 63 | 64 | _messageTextViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMessageTextViewTap:)]; 65 | [_messageTextViewTap setDelegate:self]; 66 | [self.messageTextView addGestureRecognizer:_messageTextViewTap]; 67 | } 68 | return self; 69 | } 70 | 71 | - (void)dealloc 72 | { 73 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 74 | } 75 | 76 | - (void)applyLayoutAttributes:(DBMessagingCollectionViewLayoutAttributes *)layoutAttributes 77 | { 78 | [super applyLayoutAttributes:layoutAttributes]; 79 | 80 | self.messageTextView.font = layoutAttributes.messageBubbleFont; 81 | 82 | if (!UIEdgeInsetsEqualToEdgeInsets(self.messageTextView.textContainerInset, layoutAttributes.messageBubbleTextViewTextContainerInsets)) { 83 | self.messageTextView.textContainerInset = layoutAttributes.messageBubbleTextViewTextContainerInsets; 84 | } 85 | } 86 | 87 | - (void)prepareForReuse 88 | { 89 | [super prepareForReuse]; 90 | 91 | self.messageTextView.dataDetectorTypes = UIDataDetectorTypeNone; 92 | self.messageTextView.text = nil; 93 | } 94 | 95 | #pragma mark - UIResponder 96 | 97 | - (BOOL)canBecomeFirstResponder 98 | { 99 | return YES; 100 | } 101 | 102 | - (BOOL)becomeFirstResponder 103 | { 104 | return [super becomeFirstResponder]; 105 | } 106 | 107 | - (BOOL)canPerformAction:(SEL)action withSender:(id)sender 108 | { 109 | return (action == @selector(copy:)); 110 | } 111 | 112 | - (void)copy:(id)sender 113 | { 114 | [[UIPasteboard generalPasteboard] setString:self.messageTextView.text]; 115 | [self resignFirstResponder]; 116 | } 117 | 118 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 119 | { 120 | if ([self isFirstResponder]) { 121 | [self resignFirstResponder]; 122 | } 123 | } 124 | 125 | #pragma mark - Setters 126 | 127 | - (void)setType:(MessageBubbleType)type 128 | { 129 | [super setType:type]; 130 | 131 | switch (self.type) { 132 | case MessageBubbleTypeIncoming: { 133 | [self.messageTextView setTextColor:[UIColor blackColor]]; 134 | [_messageTextView setLinkTextAttributes:@{ 135 | NSForegroundColorAttributeName : [UIColor blackColor], 136 | NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid)}]; 137 | break; 138 | } 139 | case MessageBubbleTypeOutgoing: { 140 | [self.messageTextView setTextColor:[UIColor whiteColor]]; 141 | [_messageTextView setLinkTextAttributes:@{ 142 | NSForegroundColorAttributeName : [UIColor whiteColor], 143 | NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid)}]; 144 | break; 145 | } 146 | default: 147 | break; 148 | } 149 | 150 | [self layoutSubviews]; 151 | } 152 | 153 | - (void)setMessageText:(NSString *)messageText 154 | { 155 | _messageText = messageText; 156 | self.messageTextView.text = [messageText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 157 | } 158 | 159 | #pragma mark - Actions 160 | 161 | - (void)handleLongPressGesture:(UILongPressGestureRecognizer *)longPress 162 | { 163 | if (longPress.state != UIGestureRecognizerStateBegan || ![self becomeFirstResponder]) { 164 | return; 165 | } 166 | 167 | UIMenuController *menu = [UIMenuController sharedMenuController]; 168 | CGRect targetRect = [self convertRect:self.messageBubbleImageView.bounds fromView:self.messageBubbleImageView]; 169 | 170 | [menu setTargetRect:CGRectInset(targetRect, 0.0f, 4.0f) inView:self]; 171 | 172 | self.messageBubbleImageView.highlighted = YES; 173 | 174 | [[NSNotificationCenter defaultCenter] addObserver:self 175 | selector:@selector(menuWillShow:) 176 | name:UIMenuControllerWillShowMenuNotification 177 | object:menu]; 178 | 179 | [menu setMenuVisible:YES animated:YES]; 180 | } 181 | 182 | - (void)handleMessageTextViewTap:(UITapGestureRecognizer *)tap 183 | { 184 | if ([self.delegate respondsToSelector:@selector(messageCell:didTapMessageBubbleImageView:)]) { 185 | [self.delegate messageCell:self didTapMessageBubbleImageView:self.messageBubbleImageView]; 186 | } 187 | 188 | CGPoint tapPoint = [tap locationInView:self.collectionView]; 189 | NSIndexPath *tappedIndexPath = [self.collectionView indexPathForItemAtPoint:tapPoint]; 190 | 191 | // The tap gesture only applys to hidden timestamps 192 | if ([self.collectionView.collectionViewLayout isKindOfClass:[DBMessagingCollectionViewHiddenTimestampFlowLayout class]]) { 193 | DBMessagingCollectionViewHiddenTimestampFlowLayout *collectionViewLayout = (DBMessagingCollectionViewHiddenTimestampFlowLayout *)self.collectionView.collectionViewLayout; 194 | collectionViewLayout.tappedIndexPath = tappedIndexPath; 195 | } 196 | } 197 | 198 | #pragma mark - UIGestureRecognizerDelegate 199 | 200 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 201 | { 202 | return YES; 203 | } 204 | 205 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 206 | { 207 | return otherGestureRecognizer == _longPress; 208 | } 209 | 210 | #pragma mark - Notifications 211 | 212 | - (void)menuWillHide:(NSNotification *)notification 213 | { 214 | self.messageBubbleImageView.highlighted = NO; 215 | _messageTextView.selectable = YES; 216 | 217 | [[NSNotificationCenter defaultCenter] removeObserver:self 218 | name:UIMenuControllerWillHideMenuNotification 219 | object:nil]; 220 | } 221 | 222 | - (void)menuWillShow:(NSNotification *)notification 223 | { 224 | // textviews are selectable to allow data detectors 225 | // however, this allows the 'copy, define, select' UIMenuController to show 226 | // which conflicts with the collection view's UIMenuController 227 | // temporarily disable 'selectable' to prevent this issue 228 | _messageTextView.selectable = NO; 229 | 230 | [[NSNotificationCenter defaultCenter] removeObserver:self 231 | name:UIMenuControllerWillShowMenuNotification 232 | object:nil]; 233 | 234 | [[NSNotificationCenter defaultCenter] addObserver:self 235 | selector:@selector(menuWillHide:) 236 | name:UIMenuControllerWillHideMenuNotification 237 | object:[notification object]]; 238 | } 239 | 240 | @end 241 | -------------------------------------------------------------------------------- /Source/Protocols/DBMessagingCollectionViewDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMessagingCollectionViewDataSource.h 3 | // 4 | // 5 | // GitHub 6 | // https://github.com/DevonBoyer/DBMessagingKit 7 | // 8 | // 9 | // Created by Devon Boyer on 2014-10-12. 10 | // Copyright (c) 2014 Devon Boyer. All rights reserved. 11 | // 12 | // Released under an MIT license: http://opensource.org/licenses/MIT 13 | // 14 | 15 | #import 16 | 17 | @class DBMessagingCollectionView; 18 | @class DBMessagingMediaCell; 19 | 20 | /** 21 | * An object that conforms to the 'DBMessagingCollectionViewDataSource' protocol is responsible for providing the data 22 | * and views required by a 'DBMessagingCollectionView'. The data source object represents your app’s messaging data model 23 | * and vends information to the collection view as needed. 24 | */ 25 | @protocol DBMessagingCollectionViewDataSource 26 | 27 | @required 28 | 29 | /** 30 | * Asks the data source for the message sender, that is, the user who is sending messages. 31 | * 32 | * @return An initialized string describing the sender. You must not return `nil` from this method. 33 | */ 34 | - (NSString *)senderUserID; 35 | 36 | /** 37 | * Asks the data source for the identifier of the user who sent the message at the speficied 38 | * indexPath in the collectionView. 39 | * 40 | * @param collectionView The object representing the collection view requesting this information. 41 | * @param indexPath The index path that specifies the location of the item. 42 | * 43 | * @return The identifier of the user who sent the message. 44 | */ 45 | - (NSString *)collectionView:(UICollectionView *)collectionView sentByUserIDForMessageAtIndexPath:(NSIndexPath *)indexPath; 46 | 47 | /** 48 | * Asks the data source for the MIME Type identifying the type of data contained in the given data object at the speficied 49 | * indexPath in the collectionView. 50 | * 51 | * @param collectionView The object representing the collection view requesting this information. 52 | * @param indexPath The index path that specifies the location of the item. 53 | * 54 | * @return A MIME Type identifying the type of data contained in the given data object. 55 | */ 56 | - (NSString *)collectionView:(UICollectionView *)collectionView mimeForMessageAtIndexPath:(NSIndexPath *)indexPath; 57 | 58 | /** 59 | * Asks the data source for the data to be embedded in the message at the specified indexPath in the collectionView. 60 | * 61 | * @discussion Returning 'nil' from this method will trigger the data source to call 62 | * collectionView:wantsPhotoForImageView:atIndexPath or collectionView:wantsMessageLocationData:atIndexPath 63 | * where the data can then be downloaded from the server if required. 64 | * 65 | * @param collectionView The object representing the collection view requesting this information. 66 | * @param indexPath The index path that specifies the location of the item. 67 | * 68 | * @return The data to be embedded in the message. 69 | */ 70 | - (id)collectionView:(UICollectionView *)collectionView valueForMessageAtIndexPath:(NSIndexPath *)indexPath; 71 | 72 | /** 73 | * Asks the data source for the bubble image that corresponds to the specified 74 | * message data item at indexPath in the collectionView. 75 | * 76 | * @param collectionView The object representing the collection view requesting this information. 77 | * @param indexPath The index path that specifies the location of the item. 78 | * 79 | * @discussion It is recommended that you utilize 'MessageBubbleController' to return valid imageViews, however you may 80 | * provide your own. 81 | * 82 | * @return A configured image. You may return 'nil' from this method if you do not want the specified item to display a 83 | * message bubble image. 84 | * 85 | * @see 'MessageBubbleController' 86 | * @see 'MessagingCollectionViewFlowLayout'. 87 | */ 88 | - (UIImageView *)collectionView:(UICollectionView *)collectionView messageBubbleForItemAtIndexPath:(NSIndexPath *)indexPath; 89 | 90 | @optional 91 | 92 | /** 93 | * Asks the data source for the attributed text to display in the 'messageBubbleTopLabel' for the specified 94 | * message data item at indexPath in the collectionView. 95 | * 96 | * @param collectionView The object representing the collection view requesting this information. 97 | * @param indexPath The index path that specifies the location of the item. 98 | * 99 | * @return A configured attributed string or 'nil' if you do not want text displayed for the item at indexPath. 100 | * Return an attributed string with 'nil' attributes to use the default attributes. 101 | * 102 | * @discussion The 'messageTopLabel' is typically used to display the sender's name. 103 | * 104 | * @see 'MessagingParentCell'. 105 | */ 106 | - (NSAttributedString *)collectionView:(UICollectionView *)collectionView messageTopLabelAttributedTextForItemAtIndexPath:(NSIndexPath *)indexPath; 107 | 108 | /** 109 | * Asks the data source for the attributed text to display in the 'cellTopLabel' for the specified 110 | * message data item at indexPath in the collectionView. 111 | * 112 | * @param collectionView The object representing the collection view requesting this information. 113 | * @param indexPath The index path that specifies the location of the item. 114 | * 115 | * @return A configured attributed string or 'nil' if you do not want text displayed for the item at indexPath. 116 | * Return an attributed string with 'nil' attributes to use the default attributes. 117 | * 118 | * @discussion The 'cellTopLabel' is typically used to display formatted timestamps. 119 | * 120 | * @see 'MessagingParentCell'. 121 | */ 122 | - (NSAttributedString *)collectionView:(UICollectionView *)collectionView cellTopLabelAttributedTextForItemAtIndexPath:(NSIndexPath *)indexPath; 123 | 124 | /** 125 | * Asks the data source for the attributed text to display in the 'cellBottomLabel' for the the specified 126 | * message data item at indexPath in the collectionView. 127 | * 128 | * @param collectionView The object representing the collection view requesting this information. 129 | * @param indexPath The index path that specifies the location of the item. 130 | * 131 | * @return A configured attributed string or 'nil' if you do not want text displayed for the item at indexPath. 132 | * Return an attributed string with 'nil' attributes to use the default attributes. 133 | * 134 | * @discussion The 'cellBottomLabel' is typically used to display delivery status. 135 | * 136 | * @see 'MessagingParentCell'. 137 | */ 138 | - (NSAttributedString *)collectionView:(UICollectionView *)collectionView cellBottomLabelAttributedTextForItemAtIndexPath:(NSIndexPath *)indexPath; 139 | 140 | /** 141 | * Asks the data source for the attributed text to display for the timestamp of the specified 142 | * message data item at indexPath in the collectionView. 143 | * 144 | * @param collectionView The object representing the collection view requesting this information. 145 | * @param indexPath The index path that specifies the location of the item. 146 | * 147 | * @discussion The timestamp is displayed when a message bubble receives a tap event. 148 | */ 149 | - (NSAttributedString *)collectionView:(UICollectionView *)collectionView timestampAttributedTextForSupplementaryViewAtIndexPath:(NSIndexPath *)indexPath; 150 | 151 | /** 152 | * Asks the data source to set the avatar to display in the imageView for the the specified message data item at 153 | * indexPath in the collectionView. This allows you to perform long-running tasks to retrieve the avatar. 154 | * 155 | * @param collectionView The object representing the collection view requesting this information. 156 | * @param imageView The imageView that the will display the avatar. 157 | * @param indexPath The index path that specifies the location of the item. 158 | */ 159 | - (void)collectionView:(UICollectionView *)collectionView wantsAvatarForImageView:(UIImageView *)imageView atIndexPath:(NSIndexPath *)indexPath; 160 | 161 | /** 162 | * Asks the data source to provide the media for a given media cell. This allows you to perform long-running tasks to retrieve or 163 | * decode the data from the 'value' and set the retrieved media data for the appropriate media view. 164 | * 165 | * @discussion The dataSource is structured in such a way so as to defer the need to perform long-running tasks for as long as 166 | * possible while preventing the need for a strict schema for a message. Note that for text messages this method is not required 167 | * as it is assumes that the value property directly contains the message. 168 | * 169 | * The value and mime can be used in conjunction in order to set the appropriate media data. The value returned by the dataSource 170 | * can be any object type. This is where you decide what type of view should be used to display the given value. 171 | * 172 | * Example values: 173 | * - The URL for the remote image or video 174 | * - A base64 endoded string representing an image or video sent from a web socket. 175 | * - A JSON string representing a geolocation. 176 | * - A UIImage retrieved from disk. 177 | * 178 | * @param collectionView The object representing the collection view requesting this information. 179 | * @param mediaCell The cell that the will display the media data. 180 | * @param indexPath The index path that specifies the location of the item. 181 | */ 182 | - (void)collectionView:(UICollectionView *)collectionView wantsMediaForMediaCell:(DBMessagingMediaCell *)mediaCell atIndexPath:(NSIndexPath *)indexPath; 183 | 184 | @end 185 | --------------------------------------------------------------------------------