├── 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 | 
4 |
5 | 
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 | 
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 |
--------------------------------------------------------------------------------