Skip to content

Commit 6bcfbaf

Browse files
committed
Merge pull request jessesquires#35 from zhigang1992/master
Combined PullRequest.
2 parents 78f2122 + c9477d3 commit 6bcfbaf

16 files changed

+397
-44
lines changed

JSMessagesTableViewController/JSBubbleMessageCell.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,22 @@
3636
#import <UIKit/UIKit.h>
3737
#import "JSBubbleView.h"
3838

39-
#define DATE_LABEL_HEIGHT 12.0f
39+
#define DATE_LABEL_HEIGHT 12.0f
40+
#define PHOTO_EDGE_INSET (UIEdgeInsets){2,4,2,4}
41+
#define PHOTO_SIZE (CGSize){40,40}
4042

4143
@interface JSBubbleMessageCell : UITableViewCell
4244

4345
#pragma mark - Initialization
4446
- (id)initWithBubbleStyle:(JSBubbleMessageStyle)style
4547
hasTimestamp:(BOOL)hasTimestamp
48+
hasAvatar:(BOOL)hasAvatar
4649
reuseIdentifier:(NSString *)reuseIdentifier;
4750

4851
#pragma mark - Message Cell
4952
- (void)setMessage:(NSString *)msg;
5053
- (void)setTimestamp:(NSDate *)date;
5154

55+
@property (strong, nonatomic) UIImageView *photoView;
56+
5257
@end

JSMessagesTableViewController/JSBubbleMessageCell.m

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ @interface JSBubbleMessageCell()
4343

4444
- (void)setup;
4545
- (void)configureTimestampLabel;
46-
- (void)configureWithStyle:(JSBubbleMessageStyle)style timestamp:(BOOL)hasTimestamp;
46+
- (void)configureWithStyle:(JSBubbleMessageStyle)style timestamp:(BOOL)hasTimestamp avatar:(BOOL)hasAvatar;
4747

4848
@end
4949

@@ -65,6 +65,11 @@ - (void)setup
6565
self.textLabel.hidden = YES;
6666
self.detailTextLabel.text = nil;
6767
self.detailTextLabel.hidden = YES;
68+
69+
UILongPressGestureRecognizer *recognizer =
70+
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
71+
[recognizer setMinimumPressDuration:0.5];
72+
[self addGestureRecognizer:recognizer];
6873
}
6974

7075
- (void)configureTimestampLabel
@@ -85,7 +90,7 @@ - (void)configureTimestampLabel
8590
[self.contentView bringSubviewToFront:self.timestampLabel];
8691
}
8792

88-
- (void)configureWithStyle:(JSBubbleMessageStyle)style timestamp:(BOOL)hasTimestamp
93+
- (void)configureWithStyle:(JSBubbleMessageStyle)style timestamp:(BOOL)hasTimestamp avatar:(BOOL)hasAvatar
8994
{
9095
CGFloat bubbleY = 0.0f;
9196

@@ -94,9 +99,28 @@ - (void)configureWithStyle:(JSBubbleMessageStyle)style timestamp:(BOOL)hasTimest
9499
bubbleY = 14.0f;
95100
}
96101

97-
CGRect frame = CGRectMake(0.0f,
102+
CGFloat rightOffsetX=0.0f;
103+
CGFloat leftOffsetX=0.0f;
104+
105+
106+
107+
if(hasAvatar){
108+
109+
if(style==JSBubbleMessageStyleIncomingDefault||style==JSBubbleMessageStyleIncomingSquare){
110+
rightOffsetX=PHOTO_EDGE_INSET.left+PHOTO_SIZE.width+PHOTO_EDGE_INSET.right;
111+
self.photoView=[[UIImageView alloc] initWithFrame:(CGRect){PHOTO_EDGE_INSET.left,self.contentView.frame.size.height-PHOTO_EDGE_INSET.bottom-PHOTO_SIZE.height,PHOTO_SIZE}];
112+
}else{
113+
leftOffsetX=PHOTO_EDGE_INSET.left+PHOTO_SIZE.width+PHOTO_EDGE_INSET.right;
114+
self.photoView=[[UIImageView alloc] initWithFrame:(CGRect){self.contentView.frame.size.width-PHOTO_EDGE_INSET.right-PHOTO_SIZE.width,self.contentView.frame.size.height-PHOTO_EDGE_INSET.bottom-PHOTO_SIZE.height,PHOTO_SIZE}];
115+
}
116+
[self.contentView addSubview:self.photoView];
117+
self.photoView.autoresizingMask= (UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin);
118+
}
119+
120+
121+
CGRect frame = CGRectMake(rightOffsetX,
98122
bubbleY,
99-
self.contentView.frame.size.width,
123+
self.contentView.frame.size.width-rightOffsetX-leftOffsetX,
100124
self.contentView.frame.size.height - self.timestampLabel.frame.size.height);
101125

102126
self.bubbleView = [[JSBubbleView alloc] initWithFrame:frame
@@ -108,12 +132,12 @@ - (void)configureWithStyle:(JSBubbleMessageStyle)style timestamp:(BOOL)hasTimest
108132
[self.contentView sendSubviewToBack:self.bubbleView];
109133
}
110134

111-
- (id)initWithBubbleStyle:(JSBubbleMessageStyle)style hasTimestamp:(BOOL)hasTimestamp reuseIdentifier:(NSString *)reuseIdentifier
135+
- (id)initWithBubbleStyle:(JSBubbleMessageStyle)style hasTimestamp:(BOOL)hasTimestamp hasAvatar:(BOOL)hasAvatar reuseIdentifier:(NSString *)reuseIdentifier
112136
{
113137
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
114138
if(self) {
115139
[self setup];
116-
[self configureWithStyle:style timestamp:hasTimestamp];
140+
[self configureWithStyle:style timestamp:hasTimestamp avatar:hasAvatar];
117141
}
118142
return self;
119143
}
@@ -139,4 +163,78 @@ - (void)setTimestamp:(NSDate *)date
139163
timeStyle:NSDateFormatterShortStyle];
140164
}
141165

166+
#pragma mark - Implement Copy
167+
- (BOOL) canBecomeFirstResponder
168+
{
169+
return YES;
170+
}
171+
172+
- (BOOL) becomeFirstResponder
173+
{
174+
return [super becomeFirstResponder];
175+
}
176+
177+
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
178+
{
179+
if (action == @selector(copy:))
180+
return YES;
181+
182+
return [super canPerformAction:action withSender:sender];
183+
}
184+
185+
- (void)copy:(id)sender {
186+
[[UIPasteboard generalPasteboard] setString:self.bubbleView.text];
187+
[self resignFirstResponder];
188+
}
189+
190+
- (void) handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer
191+
{
192+
if (longPressRecognizer.state != UIGestureRecognizerStateBegan)
193+
return;
194+
195+
if ([self becomeFirstResponder] == NO)
196+
return;
197+
198+
UIMenuController *menu = [UIMenuController sharedMenuController];
199+
[menu setTargetRect:CGRectInset([self.bubbleView bubbleFrame], 0, 4.f) inView:self];
200+
201+
[[NSNotificationCenter defaultCenter] addObserver:self
202+
selector:@selector(menuWillShow:)
203+
name:UIMenuControllerWillShowMenuNotification
204+
object:nil];
205+
[menu setMenuVisible:YES animated:YES];
206+
}
207+
208+
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
209+
{
210+
[super touchesEnded:touches withEvent:event];
211+
if ([self isFirstResponder] == NO)
212+
return;
213+
214+
UIMenuController *menu = [UIMenuController sharedMenuController];
215+
[menu setMenuVisible:NO animated:YES];
216+
[menu update];
217+
[self resignFirstResponder];
218+
}
219+
220+
- (void) menuWillHide:(NSNotification *)notification
221+
{
222+
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillHideMenuNotification object:nil];
223+
self.bubbleView.selectedToShowCopyMenu = NO;
224+
}
225+
226+
- (void) menuWillShow:(NSNotification *)notification
227+
{
228+
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillShowMenuNotification object:nil];
229+
[[NSNotificationCenter defaultCenter] addObserver:self
230+
selector:@selector(menuWillHide:)
231+
name:UIMenuControllerWillHideMenuNotification
232+
object:nil];
233+
self.bubbleView.selectedToShowCopyMenu = YES;
234+
}
235+
236+
- (void)dealloc{
237+
[[NSNotificationCenter defaultCenter] removeObserver:self];
238+
}
239+
142240
@end

JSMessagesTableViewController/JSBubbleView.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ typedef enum {
4040
JSBubbleMessageStyleIncomingSquare,
4141
JSBubbleMessageStyleOutgoingDefault,
4242
JSBubbleMessageStyleOutgoingDefaultGreen,
43-
JSBubbleMessageStyleOutgoingSquare
43+
JSBubbleMessageStyleOutgoingSquare,
4444
} JSBubbleMessageStyle;
4545

4646

@@ -53,6 +53,9 @@ typedef enum {
5353
#pragma mark - Initialization
5454
- (id)initWithFrame:(CGRect)frame bubbleStyle:(JSBubbleMessageStyle)bubbleStyle;
5555

56+
- (CGRect)bubbleFrame;
57+
@property (nonatomic) BOOL selectedToShowCopyMenu;
58+
5659
#pragma mark - Bubble view
5760
+ (UIImage *)bubbleImageForStyle:(JSBubbleMessageStyle)style;
5861
+ (UIFont *)font;

JSMessagesTableViewController/JSBubbleView.m

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,36 @@ - (void)setText:(NSString *)newText
8686
[self setNeedsDisplay];
8787
}
8888

89+
- (void)setSelectedToShowCopyMenu:(BOOL)selectedToShowCopyMenu{
90+
_selectedToShowCopyMenu = selectedToShowCopyMenu;
91+
[self setNeedsDisplay];
92+
}
93+
8994
#pragma mark - Drawing
90-
- (void)drawRect:(CGRect)frame
91-
{
92-
UIImage *image = [JSBubbleView bubbleImageForStyle:self.style];
95+
96+
- (CGRect)bubbleFrame{
9397
CGSize bubbleSize = [JSBubbleView bubbleSizeForText:self.text];
9498
CGRect bubbleFrame = CGRectMake(([self styleIsOutgoing] ? self.frame.size.width - bubbleSize.width : 0.0f),
9599
kMarginTop,
96100
bubbleSize.width,
97101
bubbleSize.height);
98-
102+
return bubbleFrame;
103+
}
104+
105+
- (void)drawRect:(CGRect)frame
106+
{
107+
UIImage *image = nil;
108+
if (self.selectedToShowCopyMenu) {
109+
if ([self styleIsOutgoing]) {
110+
image = [[UIImage imageNamed:@"messageBubbleHighlighted"] stretchableImageWithLeftCapWidth:15 topCapHeight:15];
111+
} else {
112+
image = [[UIImage imageNamed:@"messageBubbleSelected"] stretchableImageWithLeftCapWidth:23 topCapHeight:15];
113+
}
114+
} else {
115+
image = [JSBubbleView bubbleImageForStyle:self.style];
116+
}
117+
[JSBubbleView bubbleImageForStyle:self.style];
118+
CGRect bubbleFrame = [self bubbleFrame];
99119
[image drawInRect:bubbleFrame];
100120

101121
CGSize textSize = [JSBubbleView textSizeForText:self.text];
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// MADismissiveTextView.h
3+
// MADismissiveTextView
4+
//
5+
// Created by Mike Ahmarani on 12-02-18.
6+
// Copyright (c) 2012 Mike Ahmarani. All rights reserved.
7+
//
8+
9+
#import <UIKit/UIKit.h>
10+
11+
@protocol MADismissiveKeyboardDelegate <NSObject>
12+
13+
@optional
14+
- (void)keyboardDidShow;
15+
- (void)keyboardDidScroll:(CGPoint)keyboardOrigin;
16+
- (void)keyboardWillBeDismissed;
17+
- (void)keyboardWillSnapBackTo:(CGPoint)keyboardOrigin;
18+
@end
19+
20+
@interface JSMADismissiveTextView : UITextView
21+
22+
@property (nonatomic, weak) id <MADismissiveKeyboardDelegate> keyboardDelegate;
23+
@property (nonatomic, strong) UIPanGestureRecognizer *dismissivePanGestureRecognizer;
24+
25+
@end
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//
2+
// MADismissiveTextView.m
3+
// MADismissiveTextView
4+
//
5+
// Created by Mike Ahmarani on 12-02-18.
6+
// Copyright (c) 2012 Mike Ahmarani. All rights reserved.
7+
//
8+
9+
#import "JSMADismissiveTextView.h"
10+
11+
@interface JSMADismissiveTextView ()
12+
13+
@property (nonatomic, strong) UIView *keyboard;
14+
@property (nonatomic, readwrite) float originalKeyboardY;
15+
16+
- (void)keyboardWillShow;
17+
- (void)keyboardDidShow;
18+
- (void)panning:(UIPanGestureRecognizer *)pan;
19+
20+
@end
21+
22+
@implementation JSMADismissiveTextView
23+
24+
@synthesize keyboard, dismissivePanGestureRecognizer, originalKeyboardY, keyboardDelegate;
25+
26+
- (void)dealloc{
27+
[[NSNotificationCenter defaultCenter] removeObserver:self];
28+
[self.dismissivePanGestureRecognizer removeTarget:self action:@selector(panning:)];
29+
self.dismissivePanGestureRecognizer = nil;
30+
self.keyboardDelegate = nil;
31+
}
32+
33+
- (id)initWithFrame:(CGRect)frame{
34+
self = [super initWithFrame:frame];
35+
if (self) {
36+
37+
self.editable = YES;
38+
self.inputAccessoryView = [[UIView alloc] init]; //Empty view, used to get a handle on the keyboard when it appears.
39+
40+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow) name:@"UIKeyboardWillShowNotification" object:nil];
41+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow) name:@"UIKeyboardDidShowNotification" object:nil];
42+
43+
}
44+
return self;
45+
}
46+
47+
- (void)setDismissivePanGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer{
48+
dismissivePanGestureRecognizer = panGestureRecognizer;
49+
[dismissivePanGestureRecognizer addTarget:self action:@selector(panning:)];
50+
}
51+
52+
- (void)keyboardWillShow{
53+
self.keyboard.hidden = NO;
54+
}
55+
56+
- (void)keyboardDidShow{
57+
self.keyboard = self.inputAccessoryView.superview;
58+
if(self.keyboardDelegate && [self.keyboardDelegate respondsToSelector:@selector(keyboardDidShow)]){
59+
[self.keyboardDelegate keyboardDidShow];
60+
}
61+
}
62+
63+
- (void)panning:(UIPanGestureRecognizer *)pan{
64+
65+
if(self.keyboard && !self.keyboard.hidden){
66+
67+
CGRect screenRect = [[UIScreen mainScreen] bounds];
68+
CGFloat screenHeight = screenRect.size.height;
69+
70+
71+
UIWindow *panWindow = [[UIApplication sharedApplication] keyWindow];
72+
CGPoint location = [pan locationInView:panWindow];
73+
CGPoint velocity = [pan velocityInView:panWindow];
74+
75+
if(pan.state == UIGestureRecognizerStateBegan){ //Gesture begining, grab origin of keyboard frame.
76+
77+
self.originalKeyboardY = self.keyboard.frame.origin.y;
78+
79+
}else if(pan.state == UIGestureRecognizerStateEnded){ //Gesture ending, complete animation of keyboard
80+
81+
if(velocity.y > 0 && self.keyboard.frame.origin.y > self.originalKeyboardY){ //Gesture ended with a flick downwards, dismiss keyboard.
82+
83+
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
84+
self.keyboard.frame = CGRectMake(0, screenHeight, self.keyboard.frame.size.width, self.keyboard.frame.size.height);
85+
if(self.keyboardDelegate && [self.keyboardDelegate respondsToSelector:@selector(keyboardWillBeDismissed)]){
86+
[self.keyboardDelegate keyboardWillBeDismissed];
87+
}
88+
}completion:^(BOOL finished){
89+
self.keyboard.hidden = YES;
90+
self.keyboard.frame = CGRectMake(0, self.originalKeyboardY, self.keyboard.frame.size.width, self.keyboard.frame.size.height);
91+
[self resignFirstResponder];
92+
}];
93+
94+
}else{ //Gesture ended with no flick or a flick upwards, snap keyboard back to original position.
95+
96+
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
97+
if(self.keyboardDelegate && [self.keyboardDelegate respondsToSelector:@selector(keyboardWillSnapBackTo:)]){
98+
[self.keyboardDelegate keyboardWillSnapBackTo:CGPointMake(0, self.originalKeyboardY)];
99+
}
100+
self.keyboard.frame = CGRectMake(0, self.originalKeyboardY, self.keyboard.frame.size.width, self.keyboard.frame.size.height);
101+
} completion:^(BOOL finished){
102+
}];
103+
104+
}
105+
106+
}else{ //Gesture is currently panning, match keyboard y to touch y.
107+
108+
if(location.y > self.keyboard.frame.origin.y || self.keyboard.frame.origin.y != self.originalKeyboardY){
109+
110+
float newKeyboardY = self.originalKeyboardY + (location.y-self.originalKeyboardY);
111+
newKeyboardY = newKeyboardY < self.originalKeyboardY ? self.originalKeyboardY:newKeyboardY;
112+
newKeyboardY = newKeyboardY > screenHeight ? screenHeight :newKeyboardY;
113+
114+
self.keyboard.frame = CGRectMake(0, newKeyboardY, self.keyboard.frame.size.width, self.keyboard.frame.size.height);
115+
116+
if(self.keyboardDelegate && [self.keyboardDelegate respondsToSelector:@selector(keyboardDidScroll:)]){
117+
[self.keyboardDelegate keyboardDidScroll:CGPointMake(0, newKeyboardY)];
118+
}
119+
120+
}
121+
122+
}
123+
}
124+
}
125+
126+
@end

0 commit comments

Comments
 (0)