Repository: RobinChao/ChatMessageTableViewController Branch: master Commit: 7b752042e5df Files: 57 Total size: 198.7 KB Directory structure: gitextract_vjulnb1g/ ├── ChatMessageTableViewController/ │ ├── ChatMessageTableViewController/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj/ │ │ │ └── Main.storyboard │ │ ├── ChatMessageTableViewController-Info.plist │ │ ├── ChatMessageTableViewController-Prefix.pch │ │ ├── Images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── MasterViewController.h │ │ ├── MasterViewController.m │ │ ├── MessageData.h │ │ ├── MessageData.m │ │ ├── ViewController.h │ │ ├── ViewController.m │ │ ├── en.lproj/ │ │ │ └── InfoPlist.strings │ │ └── main.m │ ├── ChatMessageTableViewController.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcshareddata/ │ │ │ │ └── ChatMessageTableViewController.xccheckout │ │ │ └── xcuserdata/ │ │ │ ├── Robin.xcuserdatad/ │ │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── YongchaoZhang.xcuserdatad/ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata/ │ │ ├── Robin.xcuserdatad/ │ │ │ ├── xcdebugger/ │ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes/ │ │ │ ├── ChatMessageTableViewController.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── YongchaoZhang.xcuserdatad/ │ │ ├── xcdebugger/ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes/ │ │ ├── ChatMessageTableViewController.xcscheme │ │ └── xcschememanagement.plist │ ├── ChatMessageTableViewControllerTests/ │ │ ├── ChatMessageTableViewControllerTests-Info.plist │ │ ├── ChatMessageTableViewControllerTests.m │ │ └── en.lproj/ │ │ └── InfoPlist.strings │ └── MessagesTableViewController/ │ ├── JSBubbleMessageCell.h │ ├── JSBubbleMessageCell.m │ ├── JSBubbleView.h │ ├── JSBubbleView.m │ ├── JSDismissiveTextView.h │ ├── JSDismissiveTextView.m │ ├── JSMessageInputView.h │ ├── JSMessageInputView.m │ ├── JSMessageSoundEffect.h │ ├── JSMessageSoundEffect.m │ ├── JSMessageTextView.h │ ├── JSMessageTextView.m │ ├── JSMessagesViewController.h │ ├── JSMessagesViewController.m │ ├── NSString+JSMessagesView.h │ ├── NSString+JSMessagesView.m │ ├── Resources/ │ │ └── Sounds/ │ │ ├── messageReceived.aiff │ │ └── messageSent.aiff │ ├── UIButton+JSMessagesView.h │ ├── UIButton+JSMessagesView.m │ ├── UIColor+JSMessagesView.h │ ├── UIColor+JSMessagesView.m │ ├── UIImage+JSMessagesView.h │ ├── UIImage+JSMessagesView.m │ ├── UIView+AnimationOptionsForCurve.h │ └── UIView+AnimationOptionsForCurve.m └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/AppDelegate.h ================================================ // // AppDelegate.h // ChatMessageTableViewController // // Created by Yongchao on 21/11/13. // Copyright (c) 2013 Yongchao. All rights reserved. // #import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @property (nonatomic,strong) UINavigationController *navigationController; @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/AppDelegate.m ================================================ // // AppDelegate.m // ChatMessageTableViewController // // Created by Yongchao on 21/11/13. // Copyright (c) 2013 Yongchao. All rights reserved. // #import "AppDelegate.h" #import "MasterViewController.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; MasterViewController *mainViewController = [[MasterViewController alloc] init]; self.navigationController = [[UINavigationController alloc] initWithRootViewController:mainViewController]; self.window.rootViewController = self.navigationController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // 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. // 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. } - (void)applicationDidEnterBackground:(UIApplication *)application { // 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. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // 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. } - (void)applicationDidBecomeActive:(UIApplication *)application { // 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. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/Base.lproj/Main.storyboard ================================================ ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/ChatMessageTableViewController-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.mrocker.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/ChatMessageTableViewController-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #import #ifndef __IPHONE_5_0 #warning "This project uses features only available in iOS SDK 5.0 and later." #endif #ifdef __OBJC__ #import #import #endif #define UIColorFromRGB(rgbValue) [UIColor \ colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \ green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \ blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/MasterViewController.h ================================================ #import @interface MasterViewController : UIViewController @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/MasterViewController.m ================================================ #import "MasterViewController.h" #import "ViewController.h" @interface MasterViewController () @end @implementation MasterViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.title = @"ChatMessageTableViewController"; UIButton *chatBtn = [UIButton buttonWithType:UIButtonTypeCustom]; chatBtn.frame = CGRectMake(0, 0, 100, 50); chatBtn.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)); [chatBtn setTitle:@"Push to Chat" forState:UIControlStateNormal]; [chatBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; [chatBtn addTarget:self action:@selector(beginChat:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:chatBtn]; } - (void)beginChat:(id)sender{ ViewController *chatViewController = [[ViewController alloc] init]; [self.navigationController pushViewController:chatViewController animated:YES]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/MessageData.h ================================================ #import @interface MessageData : NSObject @property (nonatomic, strong) NSString *msgId; @property (nonatomic, strong) NSString *text; @property (nonatomic, strong) NSDate *date; @property (nonatomic, assign) NSInteger messageType; @property (nonatomic, assign) NSInteger mediaType; @property (nonatomic, strong) NSString *img; - (instancetype)initWithMsgId:(NSString *)msgId text:(NSString *)text date:(NSDate *)date msgType:(NSInteger)msgType mediaType:(NSInteger)medType img:(NSString *)img; @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/MessageData.m ================================================ #import "MessageData.h" @implementation MessageData - (instancetype)initWithMsgId:(NSString *)msgId text:(NSString *)text date:(NSDate *)date msgType:(NSInteger)msgType mediaType:(NSInteger)medType img:(NSString *)img{ self = [super init]; if (self) { _msgId = msgId; _text = text; _date = date; _messageType = msgType; _mediaType = medType; _img = img; } return self; } @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/ViewController.h ================================================ #import "JSMessagesViewController.h" @interface ViewController : JSMessagesViewController @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/ViewController.m ================================================ #import "ViewController.h" #import "MessageData.h" @interface ViewController () @property (strong, nonatomic) NSMutableArray *messageArray; @property (nonatomic,strong) UIImage *willSendImage; @end @implementation ViewController @synthesize messageArray; #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; self.title = @"ChatMessage"; self.delegate = self; self.dataSource = self; self.messageArray = [NSMutableArray array]; [self testData]; } - (void)testData{ MessageData *message1 = [[MessageData alloc] initWithMsgId:@"0001" text:@"This is a Chat Demo like iMessage.app" date:[NSDate date] msgType:JSBubbleMessageTypeIncoming mediaType:JSBubbleMediaTypeText img:nil]; [self.messageArray addObject:message1]; MessageData *message2 = [[MessageData alloc] initWithMsgId:@"0002" text:nil date:[NSDate date] msgType:JSBubbleMessageTypeOutgoing mediaType:JSBubbleMediaTypeImage img:@"demo1.jpg"]; [self.messageArray addObject:message2]; MessageData *message3 = [[MessageData alloc] initWithMsgId:@"0003" text:@"Up-to-date for iOS 6.0 and ARC (iOS 5.0+ required) Universal for iPhone Allows arbitrary message (and bubble) sizes Copy & paste text message && Save image message " date:[NSDate date] msgType:JSBubbleMessageTypeOutgoing mediaType:JSBubbleMediaTypeText img:nil]; [self.messageArray addObject:message3]; } #pragma mark - Table view data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.messageArray.count; } #pragma mark - Messages view delegate - (void)sendPressed:(UIButton *)sender withText:(NSString *)text { int value = arc4random() % 1000; NSString *msgId = [NSString stringWithFormat:@"%d",value]; JSBubbleMessageType msgType; if((self.messageArray.count - 1) % 2){ msgType = JSBubbleMessageTypeOutgoing; [JSMessageSoundEffect playMessageSentSound]; }else{ msgType = JSBubbleMessageTypeIncoming; [JSMessageSoundEffect playMessageReceivedSound]; } MessageData *message = [[MessageData alloc] initWithMsgId:msgId text:text date:[NSDate date] msgType:msgType mediaType:JSBubbleMediaTypeText img:nil]; [self.messageArray addObject:message]; [self finishSend:NO]; } - (void)cameraPressed:(id)sender{ [self.inputToolBarView.textView resignFirstResponder]; UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:@"拍照",@"相册", nil]; [actionSheet showInView:self.view]; } #pragma mark -- UIActionSheet Delegate -(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{ switch (buttonIndex) { case 0: case 1:{ int value = arc4random() % 1000; NSString *msgId = [NSString stringWithFormat:@"%d",value]; JSBubbleMessageType msgType; if((self.messageArray.count - 1) % 2){ msgType = JSBubbleMessageTypeOutgoing; [JSMessageSoundEffect playMessageSentSound]; }else{ msgType = JSBubbleMessageTypeIncoming; [JSMessageSoundEffect playMessageReceivedSound]; } MessageData *message = [[MessageData alloc] initWithMsgId:msgId text:nil date:[NSDate date] msgType:msgType mediaType:JSBubbleMediaTypeImage img:@"demo1.jpg"]; [self.messageArray addObject:message]; [self finishSend:YES]; } break; } } - (JSBubbleMessageType)messageTypeForRowAtIndexPath:(NSIndexPath *)indexPath { MessageData *message = self.messageArray[indexPath.row]; return message.messageType; } - (JSBubbleMessageStyle)messageStyleForRowAtIndexPath:(NSIndexPath *)indexPath { return JSBubbleMessageStyleFlat; } - (JSBubbleMediaType)messageMediaTypeForRowAtIndexPath:(NSIndexPath *)indexPath{ MessageData *message = self.messageArray[indexPath.row]; return message.mediaType; } - (UIButton *)sendButton { return [UIButton defaultSendButton]; } - (JSMessagesViewTimestampPolicy)timestampPolicy { /* JSMessagesViewTimestampPolicyAll = 0, JSMessagesViewTimestampPolicyAlternating, JSMessagesViewTimestampPolicyEveryThree, JSMessagesViewTimestampPolicyEveryFive, JSMessagesViewTimestampPolicyCustom */ return JSMessagesViewTimestampPolicyEveryThree; } - (JSMessagesViewAvatarPolicy)avatarPolicy { /* JSMessagesViewAvatarPolicyIncomingOnly = 0, JSMessagesViewAvatarPolicyBoth, JSMessagesViewAvatarPolicyNone */ return JSMessagesViewAvatarPolicyBoth; } - (JSAvatarStyle)avatarStyle { /* JSAvatarStyleCircle = 0, JSAvatarStyleSquare, JSAvatarStyleNone */ return JSAvatarStyleCircle; } - (JSInputBarStyle)inputBarStyle { /* JSInputBarStyleDefault, JSInputBarStyleFlat */ return JSInputBarStyleFlat; } // Optional delegate method // Required if using `JSMessagesViewTimestampPolicyCustom` // // - (BOOL)hasTimestampForRowAtIndexPath:(NSIndexPath *)indexPath // #pragma mark - Messages view data source - (NSString *)textForRowAtIndexPath:(NSIndexPath *)indexPath { MessageData *message = self.messageArray[indexPath.row]; return message.text; } - (NSDate *)timestampForRowAtIndexPath:(NSIndexPath *)indexPath { MessageData *message = self.messageArray[indexPath.row]; return message.date; } - (UIImage *)avatarImageForIncomingMessage { return [UIImage imageNamed:@"demo-avatar-jobs"]; } - (SEL)avatarImageForIncomingMessageAction { return @selector(onInComingAvatarImageClick); } - (void)onInComingAvatarImageClick { NSLog(@"__%s__",__func__); } - (SEL)avatarImageForOutgoingMessageAction { return @selector(onOutgoingAvatarImageClick); } - (void)onOutgoingAvatarImageClick { NSLog(@"__%s__",__func__); } - (UIImage *)avatarImageForOutgoingMessage { return [UIImage imageNamed:@"demo-avatar-woz"]; } - (id)dataForRowAtIndexPath:(NSIndexPath *)indexPath{ MessageData *message = self.messageArray[indexPath.row]; return [UIImage imageNamed:message.img]; } @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController/main.m ================================================ // // main.m // ChatMessageTableViewController // // Created by Yongchao on 21/11/13. // Copyright (c) 2013 Yongchao. All rights reserved. // #import #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 00A721331A0A0FEE00962B23 /* MessageData.m in Sources */ = {isa = PBXBuildFile; fileRef = 00A721321A0A0FEE00962B23 /* MessageData.m */; }; 00A721351A0A126800962B23 /* demo1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 00A721341A0A126800962B23 /* demo1.jpg */; }; 00A721381A0A1BDC00962B23 /* MasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 00A721371A0A1BDC00962B23 /* MasterViewController.m */; }; AFC25398183E3468001D7263 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFC25397183E3468001D7263 /* Foundation.framework */; }; AFC2539A183E3468001D7263 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFC25399183E3468001D7263 /* CoreGraphics.framework */; }; AFC2539C183E3468001D7263 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFC2539B183E3468001D7263 /* UIKit.framework */; }; AFC253A2183E3468001D7263 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AFC253A0183E3468001D7263 /* InfoPlist.strings */; }; AFC253A4183E3468001D7263 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253A3183E3468001D7263 /* main.m */; }; AFC253A8183E3468001D7263 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253A7183E3468001D7263 /* AppDelegate.m */; }; AFC253AB183E3468001D7263 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AFC253A9183E3468001D7263 /* Main.storyboard */; }; AFC253AE183E3468001D7263 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253AD183E3468001D7263 /* ViewController.m */; }; AFC253B0183E3468001D7263 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFC253AF183E3468001D7263 /* Images.xcassets */; }; AFC253B7183E3469001D7263 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFC253B6183E3469001D7263 /* XCTest.framework */; }; AFC253B8183E3469001D7263 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFC25397183E3468001D7263 /* Foundation.framework */; }; AFC253B9183E3469001D7263 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFC2539B183E3468001D7263 /* UIKit.framework */; }; AFC253C1183E3469001D7263 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AFC253BF183E3469001D7263 /* InfoPlist.strings */; }; AFC253C3183E3469001D7263 /* ChatMessageTableViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253C2183E3469001D7263 /* ChatMessageTableViewControllerTests.m */; }; AFC25413183E3489001D7263 /* JSBubbleMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253CE183E3489001D7263 /* JSBubbleMessageCell.m */; }; AFC25414183E3489001D7263 /* JSBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253D0183E3489001D7263 /* JSBubbleView.m */; }; AFC25415183E3489001D7263 /* JSDismissiveTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253D2183E3489001D7263 /* JSDismissiveTextView.m */; }; AFC25416183E3489001D7263 /* JSMessageInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253D4183E3489001D7263 /* JSMessageInputView.m */; }; AFC25417183E3489001D7263 /* JSMessageSoundEffect.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253D6183E3489001D7263 /* JSMessageSoundEffect.m */; }; AFC25418183E3489001D7263 /* JSMessagesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253D8183E3489001D7263 /* JSMessagesViewController.m */; }; AFC25419183E3489001D7263 /* JSMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253DA183E3489001D7263 /* JSMessageTextView.m */; }; AFC2541A183E3489001D7263 /* NSString+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC253DC183E3489001D7263 /* NSString+JSMessagesView.m */; }; AFC25446183E3489001D7263 /* UIButton+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC2540C183E3489001D7263 /* UIButton+JSMessagesView.m */; }; AFC25447183E3489001D7263 /* UIColor+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC2540E183E3489001D7263 /* UIColor+JSMessagesView.m */; }; AFC25448183E3489001D7263 /* UIImage+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC25410183E3489001D7263 /* UIImage+JSMessagesView.m */; }; AFC25449183E3489001D7263 /* UIView+AnimationOptionsForCurve.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC25412183E3489001D7263 /* UIView+AnimationOptionsForCurve.m */; }; AFC2547B183E369F001D7263 /* bubble-default-incoming-green.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2544C183E369E001D7263 /* bubble-default-incoming-green.png */; }; AFC2547C183E369F001D7263 /* bubble-default-incoming-green@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2544D183E369E001D7263 /* bubble-default-incoming-green@2x.png */; }; AFC2547D183E369F001D7263 /* bubble-default-incoming-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2544E183E369E001D7263 /* bubble-default-incoming-selected.png */; }; AFC2547E183E369F001D7263 /* bubble-default-incoming-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2544F183E369E001D7263 /* bubble-default-incoming-selected@2x.png */; }; AFC2547F183E369F001D7263 /* bubble-default-incoming.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25450183E369E001D7263 /* bubble-default-incoming.png */; }; AFC25480183E369F001D7263 /* bubble-default-incoming@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25451183E369E001D7263 /* bubble-default-incoming@2x.png */; }; AFC25481183E369F001D7263 /* bubble-default-outgoing-green.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25452183E369E001D7263 /* bubble-default-outgoing-green.png */; }; AFC25482183E369F001D7263 /* bubble-default-outgoing-green@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25453183E369E001D7263 /* bubble-default-outgoing-green@2x.png */; }; AFC25483183E369F001D7263 /* bubble-default-outgoing-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25454183E369E001D7263 /* bubble-default-outgoing-selected.png */; }; AFC25484183E369F001D7263 /* bubble-default-outgoing-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25455183E369E001D7263 /* bubble-default-outgoing-selected@2x.png */; }; AFC25485183E369F001D7263 /* bubble-default-outgoing.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25456183E369E001D7263 /* bubble-default-outgoing.png */; }; AFC25486183E369F001D7263 /* bubble-default-outgoing@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25457183E369E001D7263 /* bubble-default-outgoing@2x.png */; }; AFC25487183E369F001D7263 /* bubble-flat-incoming-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25458183E369E001D7263 /* bubble-flat-incoming-selected.png */; }; AFC25488183E369F001D7263 /* bubble-flat-incoming-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25459183E369E001D7263 /* bubble-flat-incoming-selected@2x.png */; }; AFC25489183E369F001D7263 /* bubble-flat-incoming.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2545A183E369E001D7263 /* bubble-flat-incoming.png */; }; AFC2548A183E369F001D7263 /* bubble-flat-incoming@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2545B183E369E001D7263 /* bubble-flat-incoming@2x.png */; }; AFC2548B183E369F001D7263 /* bubble-flat-outgoing-old.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2545C183E369E001D7263 /* bubble-flat-outgoing-old.png */; }; AFC2548C183E369F001D7263 /* bubble-flat-outgoing-old@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2545D183E369E001D7263 /* bubble-flat-outgoing-old@2x.png */; }; AFC2548D183E369F001D7263 /* bubble-flat-outgoing-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2545E183E369E001D7263 /* bubble-flat-outgoing-selected.png */; }; AFC2548E183E369F001D7263 /* bubble-flat-outgoing-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2545F183E369F001D7263 /* bubble-flat-outgoing-selected@2x.png */; }; AFC2548F183E369F001D7263 /* bubble-flat-outgoing.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25460183E369F001D7263 /* bubble-flat-outgoing.png */; }; AFC25490183E369F001D7263 /* bubble-flat-outgoing@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25461183E369F001D7263 /* bubble-flat-outgoing@2x.png */; }; AFC25491183E369F001D7263 /* bubble-square-incoming-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25462183E369F001D7263 /* bubble-square-incoming-selected.png */; }; AFC25492183E369F001D7263 /* bubble-square-incoming-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25463183E369F001D7263 /* bubble-square-incoming-selected@2x.png */; }; AFC25493183E369F001D7263 /* bubble-square-incoming.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25464183E369F001D7263 /* bubble-square-incoming.png */; }; AFC25494183E369F001D7263 /* bubble-square-incoming@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25465183E369F001D7263 /* bubble-square-incoming@2x.png */; }; AFC25495183E369F001D7263 /* bubble-square-outgoing-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25466183E369F001D7263 /* bubble-square-outgoing-selected.png */; }; AFC25496183E369F001D7263 /* bubble-square-outgoing-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25467183E369F001D7263 /* bubble-square-outgoing-selected@2x.png */; }; AFC25497183E369F001D7263 /* bubble-square-outgoing.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25468183E369F001D7263 /* bubble-square-outgoing.png */; }; AFC25498183E369F001D7263 /* bubble-square-outgoing@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25469183E369F001D7263 /* bubble-square-outgoing@2x.png */; }; AFC25499183E369F001D7263 /* bubble-typing.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2546A183E369F001D7263 /* bubble-typing.png */; }; AFC2549A183E369F001D7263 /* bubble-typing@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2546B183E369F001D7263 /* bubble-typing@2x.png */; }; AFC2549B183E369F001D7263 /* input-bar-flat.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2546C183E369F001D7263 /* input-bar-flat.png */; }; AFC2549C183E369F001D7263 /* input-bar-flat@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2546D183E369F001D7263 /* input-bar-flat@2x.png */; }; AFC2549D183E369F001D7263 /* input-bar.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2546E183E369F001D7263 /* input-bar.png */; }; AFC2549E183E369F001D7263 /* input-bar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC2546F183E369F001D7263 /* input-bar@2x.png */; }; AFC2549F183E369F001D7263 /* input-field.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25470183E369F001D7263 /* input-field.png */; }; AFC254A0183E369F001D7263 /* input-field@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25471183E369F001D7263 /* input-field@2x.png */; }; AFC254A1183E369F001D7263 /* PhotoIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25472183E369F001D7263 /* PhotoIcon.png */; }; AFC254A2183E369F001D7263 /* PhotoIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25473183E369F001D7263 /* PhotoIcon@2x.png */; }; AFC254A3183E369F001D7263 /* send-highlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25474183E369F001D7263 /* send-highlighted.png */; }; AFC254A4183E369F001D7263 /* send-highlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25475183E369F001D7263 /* send-highlighted@2x.png */; }; AFC254A5183E369F001D7263 /* send.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25476183E369F001D7263 /* send.png */; }; AFC254A6183E369F001D7263 /* send@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC25477183E369F001D7263 /* send@2x.png */; }; AFC254A7183E369F001D7263 /* messageReceived.aiff in Resources */ = {isa = PBXBuildFile; fileRef = AFC25479183E369F001D7263 /* messageReceived.aiff */; }; AFC254A8183E369F001D7263 /* messageSent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = AFC2547A183E369F001D7263 /* messageSent.aiff */; }; AFC254AB183E3744001D7263 /* demo-avatar-jobs.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC254A9183E3744001D7263 /* demo-avatar-jobs.png */; }; AFC254AC183E3744001D7263 /* demo-avatar-woz.png in Resources */ = {isa = PBXBuildFile; fileRef = AFC254AA183E3744001D7263 /* demo-avatar-woz.png */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ AFC253BA183E3469001D7263 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AFC2538C183E3468001D7263 /* Project object */; proxyType = 1; remoteGlobalIDString = AFC25393183E3468001D7263; remoteInfo = ChatMessageTableViewController; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 00A721311A0A0FEE00962B23 /* MessageData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MessageData.h; sourceTree = ""; }; 00A721321A0A0FEE00962B23 /* MessageData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MessageData.m; sourceTree = ""; }; 00A721341A0A126800962B23 /* demo1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = demo1.jpg; sourceTree = ""; }; 00A721361A0A1BDC00962B23 /* MasterViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = ""; }; 00A721371A0A1BDC00962B23 /* MasterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; AFC25394183E3468001D7263 /* ChatMessageTableViewController.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatMessageTableViewController.app; sourceTree = BUILT_PRODUCTS_DIR; }; AFC25397183E3468001D7263 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; AFC25399183E3468001D7263 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; AFC2539B183E3468001D7263 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; AFC2539F183E3468001D7263 /* ChatMessageTableViewController-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ChatMessageTableViewController-Info.plist"; sourceTree = ""; }; AFC253A1183E3468001D7263 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; AFC253A3183E3468001D7263 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; AFC253A5183E3468001D7263 /* ChatMessageTableViewController-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ChatMessageTableViewController-Prefix.pch"; sourceTree = ""; }; AFC253A6183E3468001D7263 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; AFC253A7183E3468001D7263 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; AFC253AA183E3468001D7263 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; AFC253AC183E3468001D7263 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; AFC253AD183E3468001D7263 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; AFC253AF183E3468001D7263 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; AFC253B5183E3469001D7263 /* ChatMessageTableViewControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChatMessageTableViewControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; AFC253B6183E3469001D7263 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; AFC253BE183E3469001D7263 /* ChatMessageTableViewControllerTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ChatMessageTableViewControllerTests-Info.plist"; sourceTree = ""; }; AFC253C0183E3469001D7263 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; AFC253C2183E3469001D7263 /* ChatMessageTableViewControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ChatMessageTableViewControllerTests.m; sourceTree = ""; }; AFC253CD183E3489001D7263 /* JSBubbleMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBubbleMessageCell.h; sourceTree = ""; }; AFC253CE183E3489001D7263 /* JSBubbleMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSBubbleMessageCell.m; sourceTree = ""; }; AFC253CF183E3489001D7263 /* JSBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBubbleView.h; sourceTree = ""; }; AFC253D0183E3489001D7263 /* JSBubbleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSBubbleView.m; sourceTree = ""; }; AFC253D1183E3489001D7263 /* JSDismissiveTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSDismissiveTextView.h; sourceTree = ""; }; AFC253D2183E3489001D7263 /* JSDismissiveTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSDismissiveTextView.m; sourceTree = ""; }; AFC253D3183E3489001D7263 /* JSMessageInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessageInputView.h; sourceTree = ""; }; AFC253D4183E3489001D7263 /* JSMessageInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSMessageInputView.m; sourceTree = ""; }; AFC253D5183E3489001D7263 /* JSMessageSoundEffect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessageSoundEffect.h; sourceTree = ""; }; AFC253D6183E3489001D7263 /* JSMessageSoundEffect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSMessageSoundEffect.m; sourceTree = ""; }; AFC253D7183E3489001D7263 /* JSMessagesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessagesViewController.h; sourceTree = ""; }; AFC253D8183E3489001D7263 /* JSMessagesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSMessagesViewController.m; sourceTree = ""; }; AFC253D9183E3489001D7263 /* JSMessageTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessageTextView.h; sourceTree = ""; }; AFC253DA183E3489001D7263 /* JSMessageTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSMessageTextView.m; sourceTree = ""; }; AFC253DB183E3489001D7263 /* NSString+JSMessagesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+JSMessagesView.h"; sourceTree = ""; }; AFC253DC183E3489001D7263 /* NSString+JSMessagesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+JSMessagesView.m"; sourceTree = ""; }; AFC2540B183E3489001D7263 /* UIButton+JSMessagesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIButton+JSMessagesView.h"; sourceTree = ""; }; AFC2540C183E3489001D7263 /* UIButton+JSMessagesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIButton+JSMessagesView.m"; sourceTree = ""; }; AFC2540D183E3489001D7263 /* UIColor+JSMessagesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+JSMessagesView.h"; sourceTree = ""; }; AFC2540E183E3489001D7263 /* UIColor+JSMessagesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+JSMessagesView.m"; sourceTree = ""; }; AFC2540F183E3489001D7263 /* UIImage+JSMessagesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+JSMessagesView.h"; sourceTree = ""; }; AFC25410183E3489001D7263 /* UIImage+JSMessagesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+JSMessagesView.m"; sourceTree = ""; }; AFC25411183E3489001D7263 /* UIView+AnimationOptionsForCurve.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+AnimationOptionsForCurve.h"; sourceTree = ""; }; AFC25412183E3489001D7263 /* UIView+AnimationOptionsForCurve.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+AnimationOptionsForCurve.m"; sourceTree = ""; }; AFC2544C183E369E001D7263 /* bubble-default-incoming-green.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-incoming-green.png"; sourceTree = ""; }; AFC2544D183E369E001D7263 /* bubble-default-incoming-green@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-incoming-green@2x.png"; sourceTree = ""; }; AFC2544E183E369E001D7263 /* bubble-default-incoming-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-incoming-selected.png"; sourceTree = ""; }; AFC2544F183E369E001D7263 /* bubble-default-incoming-selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-incoming-selected@2x.png"; sourceTree = ""; }; AFC25450183E369E001D7263 /* bubble-default-incoming.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-incoming.png"; sourceTree = ""; }; AFC25451183E369E001D7263 /* bubble-default-incoming@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-incoming@2x.png"; sourceTree = ""; }; AFC25452183E369E001D7263 /* bubble-default-outgoing-green.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-outgoing-green.png"; sourceTree = ""; }; AFC25453183E369E001D7263 /* bubble-default-outgoing-green@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-outgoing-green@2x.png"; sourceTree = ""; }; AFC25454183E369E001D7263 /* bubble-default-outgoing-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-outgoing-selected.png"; sourceTree = ""; }; AFC25455183E369E001D7263 /* bubble-default-outgoing-selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-outgoing-selected@2x.png"; sourceTree = ""; }; AFC25456183E369E001D7263 /* bubble-default-outgoing.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-outgoing.png"; sourceTree = ""; }; AFC25457183E369E001D7263 /* bubble-default-outgoing@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-default-outgoing@2x.png"; sourceTree = ""; }; AFC25458183E369E001D7263 /* bubble-flat-incoming-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-incoming-selected.png"; sourceTree = ""; }; AFC25459183E369E001D7263 /* bubble-flat-incoming-selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-incoming-selected@2x.png"; sourceTree = ""; }; AFC2545A183E369E001D7263 /* bubble-flat-incoming.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-incoming.png"; sourceTree = ""; }; AFC2545B183E369E001D7263 /* bubble-flat-incoming@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-incoming@2x.png"; sourceTree = ""; }; AFC2545C183E369E001D7263 /* bubble-flat-outgoing-old.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-outgoing-old.png"; sourceTree = ""; }; AFC2545D183E369E001D7263 /* bubble-flat-outgoing-old@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-outgoing-old@2x.png"; sourceTree = ""; }; AFC2545E183E369E001D7263 /* bubble-flat-outgoing-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-outgoing-selected.png"; sourceTree = ""; }; AFC2545F183E369F001D7263 /* bubble-flat-outgoing-selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-outgoing-selected@2x.png"; sourceTree = ""; }; AFC25460183E369F001D7263 /* bubble-flat-outgoing.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-outgoing.png"; sourceTree = ""; }; AFC25461183E369F001D7263 /* bubble-flat-outgoing@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-flat-outgoing@2x.png"; sourceTree = ""; }; AFC25462183E369F001D7263 /* bubble-square-incoming-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-square-incoming-selected.png"; sourceTree = ""; }; AFC25463183E369F001D7263 /* bubble-square-incoming-selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-square-incoming-selected@2x.png"; sourceTree = ""; }; AFC25464183E369F001D7263 /* bubble-square-incoming.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-square-incoming.png"; sourceTree = ""; }; AFC25465183E369F001D7263 /* bubble-square-incoming@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-square-incoming@2x.png"; sourceTree = ""; }; AFC25466183E369F001D7263 /* bubble-square-outgoing-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-square-outgoing-selected.png"; sourceTree = ""; }; AFC25467183E369F001D7263 /* bubble-square-outgoing-selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-square-outgoing-selected@2x.png"; sourceTree = ""; }; AFC25468183E369F001D7263 /* bubble-square-outgoing.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-square-outgoing.png"; sourceTree = ""; }; AFC25469183E369F001D7263 /* bubble-square-outgoing@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-square-outgoing@2x.png"; sourceTree = ""; }; AFC2546A183E369F001D7263 /* bubble-typing.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-typing.png"; sourceTree = ""; }; AFC2546B183E369F001D7263 /* bubble-typing@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-typing@2x.png"; sourceTree = ""; }; AFC2546C183E369F001D7263 /* input-bar-flat.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-bar-flat.png"; sourceTree = ""; }; AFC2546D183E369F001D7263 /* input-bar-flat@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-bar-flat@2x.png"; sourceTree = ""; }; AFC2546E183E369F001D7263 /* input-bar.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-bar.png"; sourceTree = ""; }; AFC2546F183E369F001D7263 /* input-bar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-bar@2x.png"; sourceTree = ""; }; AFC25470183E369F001D7263 /* input-field.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-field.png"; sourceTree = ""; }; AFC25471183E369F001D7263 /* input-field@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-field@2x.png"; sourceTree = ""; }; AFC25472183E369F001D7263 /* PhotoIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = PhotoIcon.png; sourceTree = ""; }; AFC25473183E369F001D7263 /* PhotoIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "PhotoIcon@2x.png"; sourceTree = ""; }; AFC25474183E369F001D7263 /* send-highlighted.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "send-highlighted.png"; sourceTree = ""; }; AFC25475183E369F001D7263 /* send-highlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "send-highlighted@2x.png"; sourceTree = ""; }; AFC25476183E369F001D7263 /* send.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = send.png; sourceTree = ""; }; AFC25477183E369F001D7263 /* send@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "send@2x.png"; sourceTree = ""; }; AFC25479183E369F001D7263 /* messageReceived.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = messageReceived.aiff; sourceTree = ""; }; AFC2547A183E369F001D7263 /* messageSent.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = messageSent.aiff; sourceTree = ""; }; AFC254A9183E3744001D7263 /* demo-avatar-jobs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "demo-avatar-jobs.png"; sourceTree = ""; }; AFC254AA183E3744001D7263 /* demo-avatar-woz.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "demo-avatar-woz.png"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ AFC25391183E3468001D7263 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( AFC2539A183E3468001D7263 /* CoreGraphics.framework in Frameworks */, AFC2539C183E3468001D7263 /* UIKit.framework in Frameworks */, AFC25398183E3468001D7263 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; AFC253B2183E3469001D7263 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( AFC253B7183E3469001D7263 /* XCTest.framework in Frameworks */, AFC253B9183E3469001D7263 /* UIKit.framework in Frameworks */, AFC253B8183E3469001D7263 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ AFC2538B183E3468001D7263 = { isa = PBXGroup; children = ( AFC2539D183E3468001D7263 /* ChatMessageTableViewController */, AFC253BC183E3469001D7263 /* ChatMessageTableViewControllerTests */, AFC25396183E3468001D7263 /* Frameworks */, AFC25395183E3468001D7263 /* Products */, ); sourceTree = ""; }; AFC25395183E3468001D7263 /* Products */ = { isa = PBXGroup; children = ( AFC25394183E3468001D7263 /* ChatMessageTableViewController.app */, AFC253B5183E3469001D7263 /* ChatMessageTableViewControllerTests.xctest */, ); name = Products; sourceTree = ""; }; AFC25396183E3468001D7263 /* Frameworks */ = { isa = PBXGroup; children = ( AFC25397183E3468001D7263 /* Foundation.framework */, AFC25399183E3468001D7263 /* CoreGraphics.framework */, AFC2539B183E3468001D7263 /* UIKit.framework */, AFC253B6183E3469001D7263 /* XCTest.framework */, ); name = Frameworks; sourceTree = ""; }; AFC2539D183E3468001D7263 /* ChatMessageTableViewController */ = { isa = PBXGroup; children = ( AFC253CC183E3489001D7263 /* MessagesTableViewController */, AFC253A6183E3468001D7263 /* AppDelegate.h */, AFC253A7183E3468001D7263 /* AppDelegate.m */, 00A721311A0A0FEE00962B23 /* MessageData.h */, 00A721321A0A0FEE00962B23 /* MessageData.m */, AFC253A9183E3468001D7263 /* Main.storyboard */, AFC253AC183E3468001D7263 /* ViewController.h */, AFC253AD183E3468001D7263 /* ViewController.m */, 00A721361A0A1BDC00962B23 /* MasterViewController.h */, 00A721371A0A1BDC00962B23 /* MasterViewController.m */, AFC253AF183E3468001D7263 /* Images.xcassets */, 00A721341A0A126800962B23 /* demo1.jpg */, AFC254A9183E3744001D7263 /* demo-avatar-jobs.png */, AFC254AA183E3744001D7263 /* demo-avatar-woz.png */, AFC2539E183E3468001D7263 /* Supporting Files */, ); path = ChatMessageTableViewController; sourceTree = ""; }; AFC2539E183E3468001D7263 /* Supporting Files */ = { isa = PBXGroup; children = ( AFC2539F183E3468001D7263 /* ChatMessageTableViewController-Info.plist */, AFC253A0183E3468001D7263 /* InfoPlist.strings */, AFC253A3183E3468001D7263 /* main.m */, AFC253A5183E3468001D7263 /* ChatMessageTableViewController-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; AFC253BC183E3469001D7263 /* ChatMessageTableViewControllerTests */ = { isa = PBXGroup; children = ( AFC253C2183E3469001D7263 /* ChatMessageTableViewControllerTests.m */, AFC253BD183E3469001D7263 /* Supporting Files */, ); path = ChatMessageTableViewControllerTests; sourceTree = ""; }; AFC253BD183E3469001D7263 /* Supporting Files */ = { isa = PBXGroup; children = ( AFC253BE183E3469001D7263 /* ChatMessageTableViewControllerTests-Info.plist */, AFC253BF183E3469001D7263 /* InfoPlist.strings */, ); name = "Supporting Files"; sourceTree = ""; }; AFC253CC183E3489001D7263 /* MessagesTableViewController */ = { isa = PBXGroup; children = ( AFC253CD183E3489001D7263 /* JSBubbleMessageCell.h */, AFC253CE183E3489001D7263 /* JSBubbleMessageCell.m */, AFC253CF183E3489001D7263 /* JSBubbleView.h */, AFC253D0183E3489001D7263 /* JSBubbleView.m */, AFC253D1183E3489001D7263 /* JSDismissiveTextView.h */, AFC253D2183E3489001D7263 /* JSDismissiveTextView.m */, AFC253D3183E3489001D7263 /* JSMessageInputView.h */, AFC253D4183E3489001D7263 /* JSMessageInputView.m */, AFC253D5183E3489001D7263 /* JSMessageSoundEffect.h */, AFC253D6183E3489001D7263 /* JSMessageSoundEffect.m */, AFC253D7183E3489001D7263 /* JSMessagesViewController.h */, AFC253D8183E3489001D7263 /* JSMessagesViewController.m */, AFC253D9183E3489001D7263 /* JSMessageTextView.h */, AFC253DA183E3489001D7263 /* JSMessageTextView.m */, AFC253DB183E3489001D7263 /* NSString+JSMessagesView.h */, AFC253DC183E3489001D7263 /* NSString+JSMessagesView.m */, AFC2540B183E3489001D7263 /* UIButton+JSMessagesView.h */, AFC2540C183E3489001D7263 /* UIButton+JSMessagesView.m */, AFC2540D183E3489001D7263 /* UIColor+JSMessagesView.h */, AFC2540E183E3489001D7263 /* UIColor+JSMessagesView.m */, AFC2540F183E3489001D7263 /* UIImage+JSMessagesView.h */, AFC25410183E3489001D7263 /* UIImage+JSMessagesView.m */, AFC25411183E3489001D7263 /* UIView+AnimationOptionsForCurve.h */, AFC25412183E3489001D7263 /* UIView+AnimationOptionsForCurve.m */, AFC2544A183E369E001D7263 /* Resources */, ); path = MessagesTableViewController; sourceTree = SOURCE_ROOT; }; AFC2544A183E369E001D7263 /* Resources */ = { isa = PBXGroup; children = ( AFC2544B183E369E001D7263 /* Images */, AFC25478183E369F001D7263 /* Sounds */, ); path = Resources; sourceTree = ""; }; AFC2544B183E369E001D7263 /* Images */ = { isa = PBXGroup; children = ( AFC2544C183E369E001D7263 /* bubble-default-incoming-green.png */, AFC2544D183E369E001D7263 /* bubble-default-incoming-green@2x.png */, AFC2544E183E369E001D7263 /* bubble-default-incoming-selected.png */, AFC2544F183E369E001D7263 /* bubble-default-incoming-selected@2x.png */, AFC25450183E369E001D7263 /* bubble-default-incoming.png */, AFC25451183E369E001D7263 /* bubble-default-incoming@2x.png */, AFC25452183E369E001D7263 /* bubble-default-outgoing-green.png */, AFC25453183E369E001D7263 /* bubble-default-outgoing-green@2x.png */, AFC25454183E369E001D7263 /* bubble-default-outgoing-selected.png */, AFC25455183E369E001D7263 /* bubble-default-outgoing-selected@2x.png */, AFC25456183E369E001D7263 /* bubble-default-outgoing.png */, AFC25457183E369E001D7263 /* bubble-default-outgoing@2x.png */, AFC25458183E369E001D7263 /* bubble-flat-incoming-selected.png */, AFC25459183E369E001D7263 /* bubble-flat-incoming-selected@2x.png */, AFC2545A183E369E001D7263 /* bubble-flat-incoming.png */, AFC2545B183E369E001D7263 /* bubble-flat-incoming@2x.png */, AFC2545C183E369E001D7263 /* bubble-flat-outgoing-old.png */, AFC2545D183E369E001D7263 /* bubble-flat-outgoing-old@2x.png */, AFC2545E183E369E001D7263 /* bubble-flat-outgoing-selected.png */, AFC2545F183E369F001D7263 /* bubble-flat-outgoing-selected@2x.png */, AFC25460183E369F001D7263 /* bubble-flat-outgoing.png */, AFC25461183E369F001D7263 /* bubble-flat-outgoing@2x.png */, AFC25462183E369F001D7263 /* bubble-square-incoming-selected.png */, AFC25463183E369F001D7263 /* bubble-square-incoming-selected@2x.png */, AFC25464183E369F001D7263 /* bubble-square-incoming.png */, AFC25465183E369F001D7263 /* bubble-square-incoming@2x.png */, AFC25466183E369F001D7263 /* bubble-square-outgoing-selected.png */, AFC25467183E369F001D7263 /* bubble-square-outgoing-selected@2x.png */, AFC25468183E369F001D7263 /* bubble-square-outgoing.png */, AFC25469183E369F001D7263 /* bubble-square-outgoing@2x.png */, AFC2546A183E369F001D7263 /* bubble-typing.png */, AFC2546B183E369F001D7263 /* bubble-typing@2x.png */, AFC2546C183E369F001D7263 /* input-bar-flat.png */, AFC2546D183E369F001D7263 /* input-bar-flat@2x.png */, AFC2546E183E369F001D7263 /* input-bar.png */, AFC2546F183E369F001D7263 /* input-bar@2x.png */, AFC25470183E369F001D7263 /* input-field.png */, AFC25471183E369F001D7263 /* input-field@2x.png */, AFC25472183E369F001D7263 /* PhotoIcon.png */, AFC25473183E369F001D7263 /* PhotoIcon@2x.png */, AFC25474183E369F001D7263 /* send-highlighted.png */, AFC25475183E369F001D7263 /* send-highlighted@2x.png */, AFC25476183E369F001D7263 /* send.png */, AFC25477183E369F001D7263 /* send@2x.png */, ); path = Images; sourceTree = ""; }; AFC25478183E369F001D7263 /* Sounds */ = { isa = PBXGroup; children = ( AFC25479183E369F001D7263 /* messageReceived.aiff */, AFC2547A183E369F001D7263 /* messageSent.aiff */, ); path = Sounds; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ AFC25393183E3468001D7263 /* ChatMessageTableViewController */ = { isa = PBXNativeTarget; buildConfigurationList = AFC253C6183E3469001D7263 /* Build configuration list for PBXNativeTarget "ChatMessageTableViewController" */; buildPhases = ( AFC25390183E3468001D7263 /* Sources */, AFC25391183E3468001D7263 /* Frameworks */, AFC25392183E3468001D7263 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ChatMessageTableViewController; productName = ChatMessageTableViewController; productReference = AFC25394183E3468001D7263 /* ChatMessageTableViewController.app */; productType = "com.apple.product-type.application"; }; AFC253B4183E3469001D7263 /* ChatMessageTableViewControllerTests */ = { isa = PBXNativeTarget; buildConfigurationList = AFC253C9183E3469001D7263 /* Build configuration list for PBXNativeTarget "ChatMessageTableViewControllerTests" */; buildPhases = ( AFC253B1183E3469001D7263 /* Sources */, AFC253B2183E3469001D7263 /* Frameworks */, AFC253B3183E3469001D7263 /* Resources */, ); buildRules = ( ); dependencies = ( AFC253BB183E3469001D7263 /* PBXTargetDependency */, ); name = ChatMessageTableViewControllerTests; productName = ChatMessageTableViewControllerTests; productReference = AFC253B5183E3469001D7263 /* ChatMessageTableViewControllerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ AFC2538C183E3468001D7263 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0610; ORGANIZATIONNAME = Yongchao; TargetAttributes = { AFC253B4183E3469001D7263 = { TestTargetID = AFC25393183E3468001D7263; }; }; }; buildConfigurationList = AFC2538F183E3468001D7263 /* Build configuration list for PBXProject "ChatMessageTableViewController" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = AFC2538B183E3468001D7263; productRefGroup = AFC25395183E3468001D7263 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( AFC25393183E3468001D7263 /* ChatMessageTableViewController */, AFC253B4183E3469001D7263 /* ChatMessageTableViewControllerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ AFC25392183E3468001D7263 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( AFC2548B183E369F001D7263 /* bubble-flat-outgoing-old.png in Resources */, AFC25486183E369F001D7263 /* bubble-default-outgoing@2x.png in Resources */, AFC254A2183E369F001D7263 /* PhotoIcon@2x.png in Resources */, AFC254AC183E3744001D7263 /* demo-avatar-woz.png in Resources */, AFC2549E183E369F001D7263 /* input-bar@2x.png in Resources */, AFC254A7183E369F001D7263 /* messageReceived.aiff in Resources */, AFC25488183E369F001D7263 /* bubble-flat-incoming-selected@2x.png in Resources */, AFC254A8183E369F001D7263 /* messageSent.aiff in Resources */, AFC2548E183E369F001D7263 /* bubble-flat-outgoing-selected@2x.png in Resources */, AFC25487183E369F001D7263 /* bubble-flat-incoming-selected.png in Resources */, AFC2549C183E369F001D7263 /* input-bar-flat@2x.png in Resources */, AFC25496183E369F001D7263 /* bubble-square-outgoing-selected@2x.png in Resources */, AFC25484183E369F001D7263 /* bubble-default-outgoing-selected@2x.png in Resources */, AFC254A0183E369F001D7263 /* input-field@2x.png in Resources */, AFC25494183E369F001D7263 /* bubble-square-incoming@2x.png in Resources */, AFC2548A183E369F001D7263 /* bubble-flat-incoming@2x.png in Resources */, AFC2549F183E369F001D7263 /* input-field.png in Resources */, AFC254A4183E369F001D7263 /* send-highlighted@2x.png in Resources */, AFC254A5183E369F001D7263 /* send.png in Resources */, AFC25480183E369F001D7263 /* bubble-default-incoming@2x.png in Resources */, AFC254AB183E3744001D7263 /* demo-avatar-jobs.png in Resources */, AFC2547C183E369F001D7263 /* bubble-default-incoming-green@2x.png in Resources */, AFC2548C183E369F001D7263 /* bubble-flat-outgoing-old@2x.png in Resources */, AFC253B0183E3468001D7263 /* Images.xcassets in Resources */, AFC25481183E369F001D7263 /* bubble-default-outgoing-green.png in Resources */, AFC25490183E369F001D7263 /* bubble-flat-outgoing@2x.png in Resources */, AFC2547D183E369F001D7263 /* bubble-default-incoming-selected.png in Resources */, AFC25497183E369F001D7263 /* bubble-square-outgoing.png in Resources */, AFC254A1183E369F001D7263 /* PhotoIcon.png in Resources */, AFC253A2183E3468001D7263 /* InfoPlist.strings in Resources */, AFC25499183E369F001D7263 /* bubble-typing.png in Resources */, AFC2548D183E369F001D7263 /* bubble-flat-outgoing-selected.png in Resources */, AFC25485183E369F001D7263 /* bubble-default-outgoing.png in Resources */, AFC25482183E369F001D7263 /* bubble-default-outgoing-green@2x.png in Resources */, AFC253AB183E3468001D7263 /* Main.storyboard in Resources */, AFC2549D183E369F001D7263 /* input-bar.png in Resources */, AFC25483183E369F001D7263 /* bubble-default-outgoing-selected.png in Resources */, AFC2547F183E369F001D7263 /* bubble-default-incoming.png in Resources */, 00A721351A0A126800962B23 /* demo1.jpg in Resources */, AFC2548F183E369F001D7263 /* bubble-flat-outgoing.png in Resources */, AFC25495183E369F001D7263 /* bubble-square-outgoing-selected.png in Resources */, AFC2547E183E369F001D7263 /* bubble-default-incoming-selected@2x.png in Resources */, AFC254A3183E369F001D7263 /* send-highlighted.png in Resources */, AFC25491183E369F001D7263 /* bubble-square-incoming-selected.png in Resources */, AFC25498183E369F001D7263 /* bubble-square-outgoing@2x.png in Resources */, AFC2549A183E369F001D7263 /* bubble-typing@2x.png in Resources */, AFC2549B183E369F001D7263 /* input-bar-flat.png in Resources */, AFC25493183E369F001D7263 /* bubble-square-incoming.png in Resources */, AFC254A6183E369F001D7263 /* send@2x.png in Resources */, AFC25492183E369F001D7263 /* bubble-square-incoming-selected@2x.png in Resources */, AFC25489183E369F001D7263 /* bubble-flat-incoming.png in Resources */, AFC2547B183E369F001D7263 /* bubble-default-incoming-green.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; AFC253B3183E3469001D7263 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( AFC253C1183E3469001D7263 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ AFC25390183E3468001D7263 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( AFC25414183E3489001D7263 /* JSBubbleView.m in Sources */, AFC25417183E3489001D7263 /* JSMessageSoundEffect.m in Sources */, AFC25419183E3489001D7263 /* JSMessageTextView.m in Sources */, AFC25448183E3489001D7263 /* UIImage+JSMessagesView.m in Sources */, AFC2541A183E3489001D7263 /* NSString+JSMessagesView.m in Sources */, 00A721381A0A1BDC00962B23 /* MasterViewController.m in Sources */, AFC253AE183E3468001D7263 /* ViewController.m in Sources */, AFC25449183E3489001D7263 /* UIView+AnimationOptionsForCurve.m in Sources */, 00A721331A0A0FEE00962B23 /* MessageData.m in Sources */, AFC25415183E3489001D7263 /* JSDismissiveTextView.m in Sources */, AFC253A8183E3468001D7263 /* AppDelegate.m in Sources */, AFC25413183E3489001D7263 /* JSBubbleMessageCell.m in Sources */, AFC253A4183E3468001D7263 /* main.m in Sources */, AFC25446183E3489001D7263 /* UIButton+JSMessagesView.m in Sources */, AFC25418183E3489001D7263 /* JSMessagesViewController.m in Sources */, AFC25416183E3489001D7263 /* JSMessageInputView.m in Sources */, AFC25447183E3489001D7263 /* UIColor+JSMessagesView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; AFC253B1183E3469001D7263 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( AFC253C3183E3469001D7263 /* ChatMessageTableViewControllerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ AFC253BB183E3469001D7263 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AFC25393183E3468001D7263 /* ChatMessageTableViewController */; targetProxy = AFC253BA183E3469001D7263 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ AFC253A0183E3468001D7263 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( AFC253A1183E3468001D7263 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; AFC253A9183E3468001D7263 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( AFC253AA183E3468001D7263 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; AFC253BF183E3469001D7263 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( AFC253C0183E3469001D7263 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ AFC253C4183E3469001D7263 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; AFC253C5183E3469001D7263 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.0; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; AFC253C7183E3469001D7263 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ChatMessageTableViewController/ChatMessageTableViewController-Prefix.pch"; INFOPLIST_FILE = "ChatMessageTableViewController/ChatMessageTableViewController-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 6.0; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "6ff61dfa-c7b9-49af-8655-0040abe6da66"; WRAPPER_EXTENSION = app; }; name = Debug; }; AFC253C8183E3469001D7263 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ChatMessageTableViewController/ChatMessageTableViewController-Prefix.pch"; INFOPLIST_FILE = "ChatMessageTableViewController/ChatMessageTableViewController-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 6.0; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "6ff61dfa-c7b9-49af-8655-0040abe6da66"; WRAPPER_EXTENSION = app; }; name = Release; }; AFC253CA183E3469001D7263 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ChatMessageTableViewController.app/ChatMessageTableViewController"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ChatMessageTableViewController/ChatMessageTableViewController-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "ChatMessageTableViewControllerTests/ChatMessageTableViewControllerTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; }; name = Debug; }; AFC253CB183E3469001D7263 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ChatMessageTableViewController.app/ChatMessageTableViewController"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ChatMessageTableViewController/ChatMessageTableViewController-Prefix.pch"; INFOPLIST_FILE = "ChatMessageTableViewControllerTests/ChatMessageTableViewControllerTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ AFC2538F183E3468001D7263 /* Build configuration list for PBXProject "ChatMessageTableViewController" */ = { isa = XCConfigurationList; buildConfigurations = ( AFC253C4183E3469001D7263 /* Debug */, AFC253C5183E3469001D7263 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; AFC253C6183E3469001D7263 /* Build configuration list for PBXNativeTarget "ChatMessageTableViewController" */ = { isa = XCConfigurationList; buildConfigurations = ( AFC253C7183E3469001D7263 /* Debug */, AFC253C8183E3469001D7263 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; AFC253C9183E3469001D7263 /* Build configuration list for PBXNativeTarget "ChatMessageTableViewControllerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( AFC253CA183E3469001D7263 /* Debug */, AFC253CB183E3469001D7263 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = AFC2538C183E3468001D7263 /* Project object */; } ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/project.xcworkspace/xcshareddata/ChatMessageTableViewController.xccheckout ================================================ IDESourceControlProjectFavoriteDictionaryKey IDESourceControlProjectIdentifier C0A7FDB5-85FC-4832-B52C-5ED6E7C87665 IDESourceControlProjectName ChatMessageTableViewController IDESourceControlProjectOriginsDictionary 3E237930A87568B09C3836C8ED5882D77B38E129 github.com:EvoRobin/ChatMessageTableViewController.git IDESourceControlProjectPath ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj IDESourceControlProjectRelativeInstallPathDictionary 3E237930A87568B09C3836C8ED5882D77B38E129 ../../.. IDESourceControlProjectURL github.com:EvoRobin/ChatMessageTableViewController.git IDESourceControlProjectVersion 111 IDESourceControlProjectWCCIdentifier 3E237930A87568B09C3836C8ED5882D77B38E129 IDESourceControlProjectWCConfigurations IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey 3E237930A87568B09C3836C8ED5882D77B38E129 IDESourceControlWCCName ChatMessageTableViewController ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/project.xcworkspace/xcuserdata/Robin.xcuserdatad/WorkspaceSettings.xcsettings ================================================ HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges SnapshotAutomaticallyBeforeSignificantChanges ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/xcuserdata/Robin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist ================================================ ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/xcuserdata/Robin.xcuserdatad/xcschemes/ChatMessageTableViewController.xcscheme ================================================ ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/xcuserdata/Robin.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState ChatMessageTableViewController.xcscheme orderHint 0 SuppressBuildableAutocreation AFC25393183E3468001D7263 primary AFC253B4183E3469001D7263 primary ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/xcuserdata/YongchaoZhang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist ================================================ ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/xcuserdata/YongchaoZhang.xcuserdatad/xcschemes/ChatMessageTableViewController.xcscheme ================================================ ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewController.xcodeproj/xcuserdata/YongchaoZhang.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState ChatMessageTableViewController.xcscheme orderHint 0 SuppressBuildableAutocreation AFC25393183E3468001D7263 primary AFC253B4183E3469001D7263 primary ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewControllerTests/ChatMessageTableViewControllerTests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier Yongchao.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewControllerTests/ChatMessageTableViewControllerTests.m ================================================ // // ChatMessageTableViewControllerTests.m // ChatMessageTableViewControllerTests // // Created by Yongchao on 21/11/13. // Copyright (c) 2013 Yongchao. All rights reserved. // #import @interface ChatMessageTableViewControllerTests : XCTestCase @end @implementation ChatMessageTableViewControllerTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); } @end ================================================ FILE: ChatMessageTableViewController/ChatMessageTableViewControllerTests/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSBubbleMessageCell.h ================================================ // // JSBubbleMessageCell.h // // Created by Jesse Squires on 2/12/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // Largely based on work by Sam Soffes // https://github.com/soffes // // SSMessagesViewController // https://github.com/soffes/ssmessagesviewcontroller // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import #import "JSBubbleView.h" typedef enum { JSAvatarStyleCircle = 0, JSAvatarStyleSquare, JSAvatarStyleNone } JSAvatarStyle; @interface JSBubbleMessageCell : UITableViewCell @property (nonatomic, assign) BOOL isSelected; @property (nonatomic, readonly, strong) UIImageView *imageView; @property (nonatomic, readonly, strong) UIImage *renderedMark; #pragma mark - Initialization - (id)initWithBubbleType:(JSBubbleMessageType)type bubbleStyle:(JSBubbleMessageStyle)bubbleStyle avatarStyle:(JSAvatarStyle)avatarStyle mediaType:(JSBubbleMediaType)mediaType hasTimestamp:(BOOL)hasTimestamp reuseIdentifier:(NSString *)reuseIdentifier; #pragma mark - Message cell - (void)setMessage:(NSString *)msg; - (void)setMedia:(id)data; - (void)setTimestamp:(NSDate *)date; - (void)setAvatarImage:(UIImage *)image; - (void)setAvatarImageTarget:(id)target action:(SEL)action; + (CGFloat)neededHeightForText:(NSString *)bubbleViewText timestamp:(BOOL)hasTimestamp avatar:(BOOL)hasAvatar; + (CGFloat)neededHeightForImage:(UIImage *)bubbleViewImage timestamp:(BOOL)hasTimestamp avatar:(BOOL)hasAvatar; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSBubbleMessageCell.m ================================================ // // JSBubbleMessageCell.m // // Created by Jesse Squires on 2/12/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // Largely based on work by Sam Soffes // https://github.com/soffes // // SSMessagesViewController // https://github.com/soffes/ssmessagesviewcontroller // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "JSBubbleMessageCell.h" #import "UIColor+JSMessagesView.h" #import "UIImage+JSMessagesView.h" #define TIMESTAMP_LABEL_HEIGHT 14.5f @interface JSBubbleMessageCell() @property (strong, nonatomic) JSBubbleView *bubbleView; @property (strong, nonatomic) UILabel *timestampLabel; @property (strong, nonatomic) UIImageView *avatarImageView; @property (assign, nonatomic) JSAvatarStyle avatarImageStyle; - (void)setup; - (void)configureTimestampLabel; - (void)configureWithType:(JSBubbleMessageType)type bubbleStyle:(JSBubbleMessageStyle)bubbleStyle avatarStyle:(JSAvatarStyle)avatarStyle mediaType:(JSBubbleMediaType)mediaType timestamp:(BOOL)hasTimestamp; - (void)handleLongPress:(UILongPressGestureRecognizer *)longPress; - (void)handleMenuWillHideNotification:(NSNotification *)notification; - (void)handleMenuWillShowNotification:(NSNotification *)notification; @end @implementation JSBubbleMessageCell #pragma mark - Setup - (void)setup { self.backgroundColor = [UIColor clearColor]; self.selectionStyle = UITableViewCellSelectionStyleNone; self.accessoryType = UITableViewCellAccessoryNone; self.accessoryView = nil; self.imageView.image = nil; self.imageView.hidden = YES; self.textLabel.text = nil; self.textLabel.hidden = YES; self.detailTextLabel.text = nil; self.detailTextLabel.hidden = YES; UILongPressGestureRecognizer *recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; [recognizer setMinimumPressDuration:0.4]; [self addGestureRecognizer:recognizer]; } - (void)configureTimestampLabel { self.timestampLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 4.0f, self.bounds.size.width, TIMESTAMP_LABEL_HEIGHT)]; self.timestampLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.timestampLabel.backgroundColor = [UIColor clearColor]; self.timestampLabel.textAlignment = NSTextAlignmentCenter; self.timestampLabel.textColor = [UIColor messagesTimestampColor]; self.timestampLabel.shadowColor = [UIColor whiteColor]; self.timestampLabel.shadowOffset = CGSizeMake(0.0f, 1.0f); self.timestampLabel.font = [UIFont boldSystemFontOfSize:11.5f]; [self.contentView addSubview:self.timestampLabel]; [self.contentView bringSubviewToFront:self.timestampLabel]; } - (void)configureWithType:(JSBubbleMessageType)type bubbleStyle:(JSBubbleMessageStyle)bubbleStyle avatarStyle:(JSAvatarStyle)avatarStyle mediaType:(JSBubbleMediaType)mediaType timestamp:(BOOL)hasTimestamp { CGFloat bubbleY = 0.0f; CGFloat bubbleX = 0.0f; if(hasTimestamp) { [self configureTimestampLabel]; bubbleY = 14.0f; } CGFloat offsetX = 0.0f; if(avatarStyle != JSAvatarStyleNone) { offsetX = 4.0f; bubbleX = kJSAvatarSize; CGFloat avatarX = 0.5f; if(type == JSBubbleMessageTypeOutgoing) { avatarX = (self.contentView.frame.size.width - kJSAvatarSize); offsetX = kJSAvatarSize - 4.0f; } self.avatarImageView = [[UIImageView alloc] initWithFrame:CGRectMake(avatarX, self.contentView.frame.size.height - kJSAvatarSize, kJSAvatarSize, kJSAvatarSize)]; self.avatarImageView.autoresizingMask = (UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin); [self.contentView addSubview:self.avatarImageView]; } CGRect frame = CGRectMake(bubbleX - offsetX, bubbleY, self.contentView.frame.size.width - bubbleX, self.contentView.frame.size.height - self.timestampLabel.frame.size.height); self.bubbleView = [[JSBubbleView alloc] initWithFrame:frame bubbleType:type bubbleStyle:bubbleStyle mediaType:mediaType]; [self.contentView addSubview:self.bubbleView]; [self.contentView sendSubviewToBack:self.bubbleView]; } #pragma mark - Initialization - (id)initWithBubbleType:(JSBubbleMessageType)type bubbleStyle:(JSBubbleMessageStyle)bubbleStyle avatarStyle:(JSAvatarStyle)avatarStyle mediaType:(JSBubbleMediaType)mediaType hasTimestamp:(BOOL)hasTimestamp reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]; if(self) { [self setup]; self.avatarImageStyle = avatarStyle; [self configureWithType:type bubbleStyle:bubbleStyle avatarStyle:avatarStyle mediaType:mediaType timestamp:hasTimestamp]; } return self; } - (void)dealloc { self.bubbleView = nil; self.timestampLabel = nil; self.avatarImageView = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - Setters - (void)setBackgroundColor:(UIColor *)color { [super setBackgroundColor:color]; [self.contentView setBackgroundColor:color]; [self.bubbleView setBackgroundColor:color]; } #pragma mark - Message Cell - (void)setMessage:(NSString *)msg { self.bubbleView.text = msg; } - (void)setMedia:(id)data { if ([data isKindOfClass:[UIImage class]]) { // image NSLog(@"show the image here"); self.bubbleView.data = data; } else if ([data isKindOfClass:[NSData class]]) { // show a button / icon to view details NSLog(@"icon view"); } } - (void)setTimestamp:(NSDate *)date { self.timestampLabel.text = [NSDateFormatter localizedStringFromDate:date dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterShortStyle]; } - (void)setAvatarImage:(UIImage *)image { UIImage *styledImg = nil; switch (self.avatarImageStyle) { case JSAvatarStyleCircle: styledImg = [image circleImageWithSize:kJSAvatarSize]; break; case JSAvatarStyleSquare: styledImg = [image squareImageWithSize:kJSAvatarSize]; break; case JSAvatarStyleNone: default: break; } self.avatarImageView.image = styledImg; } - (void)setAvatarImageTarget:(id)target action:(SEL)action { self.avatarImageView.userInteractionEnabled = YES; UITapGestureRecognizer *actionTap = [[UITapGestureRecognizer alloc] initWithTarget:target action:action]; [self.avatarImageView addGestureRecognizer:actionTap]; } + (CGFloat)neededHeightForText:(NSString *)bubbleViewText timestamp:(BOOL)hasTimestamp avatar:(BOOL)hasAvatar { CGFloat timestampHeight = (hasTimestamp) ? TIMESTAMP_LABEL_HEIGHT : 0.0f; CGFloat avatarHeight = (hasAvatar) ? kJSAvatarSize : 0.0f; return MAX(avatarHeight, [JSBubbleView cellHeightForText:bubbleViewText]) + timestampHeight; } + (CGFloat)neededHeightForImage:(UIImage *)bubbleViewImage timestamp:(BOOL)hasTimestamp avatar:(BOOL)hasAvatar{ CGFloat timestampHeight = (hasTimestamp) ? TIMESTAMP_LABEL_HEIGHT : 0.0f; CGFloat avatarHeight = (hasAvatar) ? kJSAvatarSize : 0.0f; return MAX(avatarHeight, [JSBubbleView cellHeightForImage:bubbleViewImage]) + timestampHeight; } #pragma mark - Copying - (BOOL)canBecomeFirstResponder { return YES; } - (BOOL)becomeFirstResponder { return [super becomeFirstResponder]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { if(self.bubbleView.data){ if(action == @selector(saveImage:)) return YES; }else{ if(action == @selector(copy:)) return YES; } return [super canPerformAction:action withSender:sender]; } - (void)copy:(id)sender { [[UIPasteboard generalPasteboard] setString:self.bubbleView.text]; [self resignFirstResponder]; } #pragma mark - Touch events - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; if(![self isFirstResponder]) return; UIMenuController *menu = [UIMenuController sharedMenuController]; [menu setMenuVisible:NO animated:YES]; [menu update]; [self resignFirstResponder]; } #pragma mark - Gestures - (void)handleLongPress:(UILongPressGestureRecognizer *)longPress { if(longPress.state != UIGestureRecognizerStateBegan || ![self becomeFirstResponder]) return; UIMenuController *menu = [UIMenuController sharedMenuController]; UIMenuItem *saveItem; if(self.bubbleView.data){ saveItem = [[UIMenuItem alloc] initWithTitle:@"Save" action:@selector(saveImage:)]; }else{ saveItem = nil; } [menu setMenuItems:[NSArray arrayWithObjects:saveItem, nil]]; CGRect targetRect = [self convertRect:[self.bubbleView bubbleFrame] fromView:self.bubbleView]; [menu setTargetRect:CGRectInset(targetRect, 0.0f, 4.0f) inView:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMenuWillShowNotification:) name:UIMenuControllerWillShowMenuNotification object:nil]; [menu setMenuVisible:YES animated:YES]; [menu update]; } #pragma mark - Save Image -(void)saveImage:(id)sender{ UIImageWriteToSavedPhotosAlbum(self.bubbleView.data, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); } - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{ UIAlertView *alertView; if (error != NULL){ alertView = [[UIAlertView alloc] initWithTitle:@"Save Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; }else{ alertView = [[UIAlertView alloc] initWithTitle:@"Save Success" message:@"Image has Saved !" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; } [alertView show]; } #pragma mark - Notification - (void)handleMenuWillHideNotification:(NSNotification *)notification { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillHideMenuNotification object:nil]; self.bubbleView.selectedToShowCopyMenu = NO; } - (void)handleMenuWillShowNotification:(NSNotification *)notification { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillShowMenuNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMenuWillHideNotification:) name:UIMenuControllerWillHideMenuNotification object:nil]; self.bubbleView.selectedToShowCopyMenu = YES; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSBubbleView.h ================================================ // // JSBubbleView.h // // Created by Jesse Squires on 2/12/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // Largely based on work by Sam Soffes // https://github.com/soffes // // SSMessagesViewController // https://github.com/soffes/ssmessagesviewcontroller // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import extern CGFloat const kJSAvatarSize; typedef NS_ENUM(NSInteger, JSBubbleMessageType) { JSBubbleMessageTypeIncoming = 0, JSBubbleMessageTypeOutgoing } ; typedef NS_ENUM(NSInteger, JSBubbleMediaType) { JSBubbleMediaTypeText = 0, JSBubbleMediaTypeImage, }; typedef enum { JSBubbleMessageStyleDefault = 0, JSBubbleMessageStyleSquare, JSBubbleMessageStyleDefaultGreen, JSBubbleMessageStyleFlat } JSBubbleMessageStyle; @interface JSBubbleView : UIView @property (assign, nonatomic) JSBubbleMessageType type; @property (assign, nonatomic) JSBubbleMessageStyle style; @property (nonatomic,assign) JSBubbleMediaType mediaType; @property (copy, nonatomic) NSString *text; @property (copy, nonatomic) id data; @property (assign, nonatomic) BOOL selectedToShowCopyMenu; #pragma mark - Initialization - (id)initWithFrame:(CGRect)rect bubbleType:(JSBubbleMessageType)bubleType bubbleStyle:(JSBubbleMessageStyle)bubbleStyle mediaType:(JSBubbleMediaType)bubbleMediaType; #pragma mark - Drawing - (CGRect)bubbleFrame; - (UIImage *)bubbleImage; - (UIImage *)bubbleImageHighlighted; #pragma mark - Bubble view + (UIImage *)bubbleImageForType:(JSBubbleMessageType)aType style:(JSBubbleMessageStyle)aStyle; + (UIFont *)font; + (CGSize)textSizeForText:(NSString *)txt; + (CGSize)bubbleSizeForText:(NSString *)txt; + (CGSize)bubbleSizeForImage:(UIImage *)image; + (CGSize)imageSizeForImage:(UIImage *)image; + (CGFloat)cellHeightForText:(NSString *)txt; + (CGFloat)cellHeightForImage:(UIImage *)image; + (int)maxCharactersPerLine; + (int)numberOfLinesForMessage:(NSString *)txt; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSBubbleView.m ================================================ // // JSBubbleView.m // // Created by Jesse Squires on 2/12/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // Largely based on work by Sam Soffes // https://github.com/soffes // // SSMessagesViewController // https://github.com/soffes/ssmessagesviewcontroller // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "JSBubbleView.h" #import "JSMessageInputView.h" #import "NSString+JSMessagesView.h" #import "UIImage+JSMessagesView.h" CGFloat const kJSAvatarSize = 50.0f; #define kMarginTop 8.0f #define kMarginBottom 4.0f #define kPaddingTop 12.0f #define kPaddingBottom 12.0f #define kBubblePaddingRight 35.0f @interface JSBubbleView() - (void)setup; + (UIImage *)bubbleImageTypeIncomingWithStyle:(JSBubbleMessageStyle)aStyle; + (UIImage *)bubbleImageTypeOutgoingWithStyle:(JSBubbleMessageStyle)aStyle; @end @implementation JSBubbleView @synthesize type; @synthesize style; @synthesize mediaType; @synthesize text; @synthesize data; @synthesize selectedToShowCopyMenu; #pragma mark - Setup - (void)setup { self.backgroundColor = [UIColor clearColor]; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; } #pragma mark - Initialization - (id)initWithFrame:(CGRect)rect bubbleType:(JSBubbleMessageType)bubleType bubbleStyle:(JSBubbleMessageStyle)bubbleStyle mediaType:(JSBubbleMediaType)bubbleMediaType { self = [super initWithFrame:rect]; if(self) { [self setup]; self.type = bubleType; self.style = bubbleStyle; self.mediaType = bubbleMediaType; } return self; } - (void)dealloc { self.text = nil; } #pragma mark - Setters - (void)setType:(JSBubbleMessageType)newType { type = newType; [self setNeedsDisplay]; } - (void)setStyle:(JSBubbleMessageStyle)newStyle { style = newStyle; [self setNeedsDisplay]; } - (void)setMediaType:(JSBubbleMediaType)newMediaType{ mediaType = newMediaType; [self setNeedsDisplay]; } - (void)setText:(NSString *)newText { text = newText; [self setNeedsDisplay]; } - (void)setData:(id)newData{ data = newData; [self setNeedsDisplay]; } - (void)setSelectedToShowCopyMenu:(BOOL)isSelected { selectedToShowCopyMenu = isSelected; [self setNeedsDisplay]; } #pragma mark - Drawing - (CGRect)bubbleFrame { if(self.mediaType == JSBubbleMediaTypeText){ CGSize bubbleSize = [JSBubbleView bubbleSizeForText:self.text]; return CGRectMake(floorf(self.type == JSBubbleMessageTypeOutgoing ? self.frame.size.width - bubbleSize.width : 0.0f), floorf(kMarginTop), floorf(bubbleSize.width), floorf(bubbleSize.height)); }else if (self.mediaType == JSBubbleMediaTypeImage){ CGSize bubbleSize = [JSBubbleView imageSizeForImage:(UIImage *)self.data]; return CGRectMake(floorf(self.type == JSBubbleMessageTypeOutgoing ? self.frame.size.width - bubbleSize.width : 10.0f), floorf(kMarginTop), floorf(bubbleSize.width), floorf(bubbleSize.height)); }else{ NSLog(@"act对象消息"); return CGRectMake(0, 0, 0, 0); } } - (UIImage *)bubbleImage { return [JSBubbleView bubbleImageForType:self.type style:self.style]; } - (UIImage *)bubbleImageHighlighted { switch (self.style) { case JSBubbleMessageStyleDefault: case JSBubbleMessageStyleDefaultGreen: return (self.type == JSBubbleMessageTypeIncoming) ? [UIImage bubbleDefaultIncomingSelected] : [UIImage bubbleDefaultOutgoingSelected]; case JSBubbleMessageStyleSquare: return (self.type == JSBubbleMessageTypeIncoming) ? [UIImage bubbleSquareIncomingSelected] : [UIImage bubbleSquareOutgoingSelected]; case JSBubbleMessageStyleFlat: return (self.type == JSBubbleMessageTypeIncoming) ? [UIImage bubbleFlatIncomingSelected] : [UIImage bubbleFlatOutgoingSelected]; default: return nil; } } - (void)drawRect:(CGRect)frame { [super drawRect:frame]; UIImage *image = (self.selectedToShowCopyMenu) ? [self bubbleImageHighlighted] : [self bubbleImage]; CGRect bubbleFrame = [self bubbleFrame]; [image drawInRect:bubbleFrame]; if (self.mediaType == JSBubbleMediaTypeText) { CGSize textSize = [JSBubbleView textSizeForText:self.text]; CGFloat textX = image.leftCapWidth - 3.0f + (self.type == JSBubbleMessageTypeOutgoing ? bubbleFrame.origin.x : 0.0f); CGRect textFrame = CGRectMake(textX, kPaddingTop + kMarginTop, textSize.width, textSize.height); // for flat outgoing messages change the text color to grey or white. Otherwise leave them black. if (self.style == JSBubbleMessageStyleFlat && self.type == JSBubbleMessageTypeOutgoing) { UIColor* textColor = [UIColor whiteColor]; if (self.selectedToShowCopyMenu) textColor = [UIColor lightTextColor]; if ([[[UIDevice currentDevice] systemVersion] compare:@"7.0" options:NSNumericSearch] != NSOrderedAscending) { NSMutableParagraphStyle* paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; [paragraphStyle setAlignment:NSTextAlignmentLeft]; [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping]; NSDictionary* attributes = @{NSFontAttributeName: [JSBubbleView font], NSParagraphStyleAttributeName: paragraphStyle}; // change the color attribute if we are flat if ([JSMessageInputView inputBarStyle] == JSInputBarStyleFlat) { NSMutableDictionary* dict = [attributes mutableCopy]; [dict setObject:textColor forKey:NSForegroundColorAttributeName]; attributes = [NSDictionary dictionaryWithDictionary:dict]; } [self.text drawInRect:textFrame withAttributes:attributes]; } else { CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), textColor.CGColor); [self.text drawInRect:textFrame withFont:[JSBubbleView font] lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentLeft]; CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [UIColor blackColor].CGColor); } } else { if ([[[UIDevice currentDevice] systemVersion] compare:@"7.0" options:NSNumericSearch] != NSOrderedAscending) { UIColor* textColor = [UIColor blackColor]; if (self.selectedToShowCopyMenu) textColor = [UIColor lightTextColor]; NSMutableParagraphStyle* paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; [paragraphStyle setAlignment:NSTextAlignmentLeft]; [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping]; NSDictionary* attributes = @{NSFontAttributeName: [JSBubbleView font], NSParagraphStyleAttributeName: paragraphStyle}; // change the color attribute if we are flat if ([JSMessageInputView inputBarStyle] == JSInputBarStyleFlat) { NSMutableDictionary* dict = [attributes mutableCopy]; [dict setObject:textColor forKey:NSForegroundColorAttributeName]; attributes = [NSDictionary dictionaryWithDictionary:dict]; } [self.text drawInRect:textFrame withAttributes:attributes]; } else { [self.text drawInRect:textFrame withFont:[JSBubbleView font] lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentLeft]; } } } else if(self.mediaType == JSBubbleMediaTypeImage) // media { UIImage *recivedImg = (UIImage *)self.data; if (recivedImg) { CGSize imageSize = [JSBubbleView imageSizeForImage:recivedImg]; CGFloat imgX = image.leftCapWidth - 3.0f + (self.type == JSBubbleMessageTypeOutgoing ? bubbleFrame.origin.x : 0.0f); CGRect imageFrame = CGRectMake(imgX - 3.f, kPaddingTop, imageSize.width - kPaddingTop - kMarginTop, imageSize.height - kPaddingBottom + 2.f); if (self.style == JSBubbleMessageStyleFlat && self.type == JSBubbleMessageTypeOutgoing) { UIColor* textColor = [UIColor whiteColor]; if (self.selectedToShowCopyMenu) textColor = [UIColor lightTextColor]; } [recivedImg drawInRect:imageFrame]; } } } #pragma mark - Bubble view + (UIImage *)bubbleImageForType:(JSBubbleMessageType)aType style:(JSBubbleMessageStyle)aStyle { switch (aType) { case JSBubbleMessageTypeIncoming: return [self bubbleImageTypeIncomingWithStyle:aStyle]; case JSBubbleMessageTypeOutgoing: return [self bubbleImageTypeOutgoingWithStyle:aStyle]; default: return nil; } } + (UIImage *)bubbleImageTypeIncomingWithStyle:(JSBubbleMessageStyle)aStyle { switch (aStyle) { case JSBubbleMessageStyleDefault: return [UIImage bubbleDefaultIncoming]; case JSBubbleMessageStyleSquare: return [UIImage bubbleSquareIncoming]; case JSBubbleMessageStyleDefaultGreen: return [UIImage bubbleDefaultIncomingGreen]; case JSBubbleMessageStyleFlat: return [UIImage bubbleFlatIncoming]; default: return nil; } } + (UIImage *)bubbleImageTypeOutgoingWithStyle:(JSBubbleMessageStyle)aStyle { switch (aStyle) { case JSBubbleMessageStyleDefault: return [UIImage bubbleDefaultOutgoing]; case JSBubbleMessageStyleSquare: return [UIImage bubbleSquareOutgoing]; case JSBubbleMessageStyleDefaultGreen: return [UIImage bubbleDefaultOutgoingGreen]; case JSBubbleMessageStyleFlat: return [UIImage bubbleFlatOutgoing]; default: return nil; } } + (UIFont *)font { return [UIFont systemFontOfSize:16.0f]; } + (CGSize)textSizeForText:(NSString *)txt { CGFloat width = [UIScreen mainScreen].applicationFrame.size.width * 0.75f; CGFloat height = MAX([JSBubbleView numberOfLinesForMessage:txt], [txt numberOfLines]) * [JSMessageInputView textViewLineHeight]; return [txt sizeWithFont:[JSBubbleView font] constrainedToSize:CGSizeMake(width - kJSAvatarSize, height + kJSAvatarSize) lineBreakMode:NSLineBreakByWordWrapping]; } + (CGSize)bubbleSizeForText:(NSString *)txt { CGSize textSize = [JSBubbleView textSizeForText:txt]; return CGSizeMake(textSize.width + kBubblePaddingRight, textSize.height + kPaddingTop + kPaddingBottom); } + (CGSize)bubbleSizeForImage:(UIImage *)image{ CGSize imageSize = [JSBubbleView imageSizeForImage:image]; return CGSizeMake(imageSize.width, imageSize.height); } + (CGSize)imageSizeForImage:(UIImage *)image{ CGFloat width = [UIScreen mainScreen].applicationFrame.size.width * 0.75f; CGFloat height = 130.f; return CGSizeMake(width - kJSAvatarSize, height + kJSAvatarSize); } + (CGFloat)cellHeightForText:(NSString *)txt { return [JSBubbleView bubbleSizeForText:txt].height + kMarginTop + kMarginBottom; } + (CGFloat)cellHeightForImage:(UIImage *)image{ return [JSBubbleView bubbleSizeForImage:image].height + kMarginTop + kMarginBottom; } + (int)maxCharactersPerLine { return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) ? 34 : 109; } + (int)numberOfLinesForMessage:(NSString *)txt { return (int)(txt.length / [JSBubbleView maxCharactersPerLine]) + 1; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSDismissiveTextView.h ================================================ // // Taken from MADismissiveTextView // https://github.com/mikeahmarani/MADismissiveTextView // // Created by Mike Ahmarani on 12-02-18. // Copyright (c) 2012 Mike Ahmarani. All rights reserved. // // // Documentation // http://cocoadocs.org/docsets/JSMessagesViewController // // // The MIT License // Copyright (c) 2013 Jesse Squires // http://opensource.org/licenses/MIT // #import /** * The delegate of a `JSDismissiveTextView` object must adopt the `JSDismissiveTextViewDelegate` protocol. */ @protocol JSDismissiveTextViewDelegate @optional /** * Tells the delegate that the keyboard has full appeared on screen. */ - (void)keyboardDidShow; /** * Tells the delegate that the keyboard origin has moved to the specified point. * * @param point The origin of the keyboard's frame in its superview's coordinate system. */ - (void)keyboardDidScrollToPoint:(CGPoint)point; /** * Tells the delegate that the keyboard is about to be dismissed. The keyboard will be removed from from its superview and resign first responder. */ - (void)keyboardWillBeDismissed; /** * Tells the delegate that the keyboard origin is about to move back to the specified point. * * @param point The new origin of the keyboard's frame after it has completed animation. */ - (void)keyboardWillSnapBackToPoint:(CGPoint)point; @end /** * An instance of `JSDismissiveTextView` is a means for displaying a text view that is contained as a subview of the keyboard's `inputAccessoryView` and responds to a pan gesture to dismiss the keyboard and end editing. */ @interface JSDismissiveTextView : UITextView /** * The object that acts as the delegate of the receiving text view. */ @property (weak, nonatomic) id keyboardDelegate; /** * The pan gesture recognizer for the text view. */ @property (strong, nonatomic) UIPanGestureRecognizer *dismissivePanGestureRecognizer; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSDismissiveTextView.m ================================================ // // Taken from MADismissiveTextView // https://github.com/mikeahmarani/MADismissiveTextView // // Created by Mike Ahmarani on 12-02-18. // Copyright (c) 2012 Mike Ahmarani. All rights reserved. // // // Documentation // http://cocoadocs.org/docsets/JSMessagesViewController // // // The MIT License // Copyright (c) 2013 Jesse Squires // http://opensource.org/licenses/MIT // #import "JSDismissiveTextView.h" @interface JSDismissiveTextView () @property (strong, nonatomic) UIView *keyboardView; @property (assign, nonatomic) CGFloat previousKeyboardY; - (void)handleKeyboardWillShowHideNotification:(NSNotification *)notification; - (void)handlePanGesture:(UIPanGestureRecognizer *)pan; @end @implementation JSDismissiveTextView #pragma mark - Initialization - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if(self) { self.editable = YES; // FIXME: this is a hack // --------------------- // init an empty inputAccessoryView to get a reference to the keyboard when it appears // i.e., self.inputAccessoryView.superview <-- the keyboard (see notification handler below) // otherwise self.inputAccessoryView == nil, thus self.inputAccessoryView.superivew == nil // --------------------- // Can you fix this? Submit a PR! :) self.inputAccessoryView = [[UIView alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleKeyboardWillShowHideNotification:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleKeyboardWillShowHideNotification:) name:UIKeyboardDidShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleKeyboardWillShowHideNotification:) name:UIKeyboardDidHideNotification object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [_dismissivePanGestureRecognizer removeTarget:self action:@selector(handlePanGesture:)]; _dismissivePanGestureRecognizer = nil; _keyboardDelegate = nil; _keyboardView = nil; } #pragma mark - Setters - (void)setDismissivePanGestureRecognizer:(UIPanGestureRecognizer *)pan { _dismissivePanGestureRecognizer = pan; [_dismissivePanGestureRecognizer addTarget:self action:@selector(handlePanGesture:)]; } #pragma mark - Notifications - (void)handleKeyboardWillShowHideNotification:(NSNotification *)notification { if([notification.name isEqualToString:UIKeyboardWillShowNotification]) { self.keyboardView.hidden = NO; } else if([notification.name isEqualToString:UIKeyboardDidShowNotification]) { self.keyboardView = self.inputAccessoryView.superview; self.keyboardView.hidden = NO; if(self.keyboardDelegate && [self.keyboardDelegate respondsToSelector:@selector(keyboardDidShow)]) [self.keyboardDelegate keyboardDidShow]; } else if([notification.name isEqualToString:UIKeyboardDidHideNotification]) { self.keyboardView.hidden = NO; [self resignFirstResponder]; } } #pragma mark - Gestures - (void)handlePanGesture:(UIPanGestureRecognizer *)pan { if(!self.keyboardView || self.keyboardView.hidden) return; CGRect screenRect = [[UIScreen mainScreen] bounds]; CGFloat screenHeight = screenRect.size.height; UIWindow *panWindow = [[UIApplication sharedApplication] keyWindow]; CGPoint location = [pan locationInView:panWindow]; CGPoint velocity = [pan velocityInView:panWindow]; switch (pan.state) { case UIGestureRecognizerStateBegan: self.previousKeyboardY = self.keyboardView.frame.origin.y; break; case UIGestureRecognizerStateEnded: if(velocity.y > 0 && self.keyboardView.frame.origin.y > self.previousKeyboardY) { [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ self.keyboardView.frame = CGRectMake(0.0f, screenHeight, self.keyboardView.frame.size.width, self.keyboardView.frame.size.height); if(self.keyboardDelegate && [self.keyboardDelegate respondsToSelector:@selector(keyboardWillBeDismissed)]) [self.keyboardDelegate keyboardWillBeDismissed]; } completion:^(BOOL finished) { self.keyboardView.hidden = YES; self.keyboardView.frame = CGRectMake(0.0f, self.previousKeyboardY, self.keyboardView.frame.size.width, self.keyboardView.frame.size.height); [self resignFirstResponder]; }]; } else { // gesture ended with no flick or a flick upwards, snap keyboard back to original position [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ if(self.keyboardDelegate && [self.keyboardDelegate respondsToSelector:@selector(keyboardWillSnapBackToPoint:)]) { [self.keyboardDelegate keyboardWillSnapBackToPoint:CGPointMake(0.0f, self.previousKeyboardY)]; } self.keyboardView.frame = CGRectMake(0.0f, self.previousKeyboardY, self.keyboardView.frame.size.width, self.keyboardView.frame.size.height); } completion:^(BOOL finished){ }]; } break; // gesture is currently panning, match keyboard y to touch y default: if(location.y > self.keyboardView.frame.origin.y || self.keyboardView.frame.origin.y != self.previousKeyboardY) { CGFloat newKeyboardY = self.previousKeyboardY + (location.y - self.previousKeyboardY); newKeyboardY = newKeyboardY < self.previousKeyboardY ? self.previousKeyboardY : newKeyboardY; newKeyboardY = newKeyboardY > screenHeight ? screenHeight : newKeyboardY; self.keyboardView.frame = CGRectMake(0.0f, newKeyboardY, self.keyboardView.frame.size.width, self.keyboardView.frame.size.height); if(self.keyboardDelegate && [self.keyboardDelegate respondsToSelector:@selector(keyboardDidScrollToPoint:)]) [self.keyboardDelegate keyboardDidScrollToPoint:CGPointMake(0.0f, newKeyboardY)]; } break; } } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSMessageInputView.h ================================================ // // JSMessageInputView.h // // Created by Jesse Squires on 2/12/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // Largely based on work by Sam Soffes // https://github.com/soffes // // SSMessagesViewController // https://github.com/soffes/ssmessagesviewcontroller // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import #import "JSMessageTextView.h" typedef enum { JSInputBarStyleDefault, JSInputBarStyleFlat } JSInputBarStyle; @protocol JSMessageInputViewDelegate @optional - (JSInputBarStyle)inputBarStyle; @end @interface JSMessageInputView : UIImageView @property (strong, nonatomic) JSMessageTextView *textView; @property (strong, nonatomic) UIButton *sendButton; #pragma mark - Initialization - (id)initWithFrame:(CGRect)frame delegate:(id)delegate; #pragma mark - Message input view - (void)adjustTextViewHeightBy:(CGFloat)changeInHeight; + (CGFloat)textViewLineHeight; + (CGFloat)maxLines; + (CGFloat)maxHeight; + (JSInputBarStyle)inputBarStyle; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSMessageInputView.m ================================================ // // JSMessageInputView.m // // Created by Jesse Squires on 2/12/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // Largely based on work by Sam Soffes // https://github.com/soffes // // SSMessagesViewController // https://github.com/soffes/ssmessagesviewcontroller // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "JSMessageInputView.h" #import "JSBubbleView.h" #import "NSString+JSMessagesView.h" #import "UIImage+JSMessagesView.h" #import "UIColor+JSMessagesView.h" #define SEND_BUTTON_WIDTH 78.0f static id __delegate; @interface JSMessageInputView () - (void)setup; - (void)setupTextView; @end @implementation JSMessageInputView @synthesize sendButton; #pragma mark - Initialization - (id)initWithFrame:(CGRect)frame delegate:(id)delegate { self = [super initWithFrame:frame]; if(self) { __delegate = delegate; [self setup]; self.textView.delegate = delegate; } return self; } + (JSInputBarStyle)inputBarStyle { if ([__delegate respondsToSelector:@selector(inputBarStyle)]) return [__delegate inputBarStyle]; return JSInputBarStyleDefault; } - (void)dealloc { self.textView = nil; self.sendButton = nil; } - (BOOL)resignFirstResponder { [self.textView resignFirstResponder]; return [super resignFirstResponder]; } #pragma mark - Setup - (void)setup { self.image = [UIImage inputBar]; self.backgroundColor = [UIColor whiteColor]; self.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin); self.opaque = YES; self.userInteractionEnabled = YES; [self setupTextView]; } - (void)setupTextView { CGFloat width = self.frame.size.width - SEND_BUTTON_WIDTH; CGFloat height = [JSMessageInputView textViewLineHeight]; // JeremyStone JSInputBarStyle style = [JSMessageInputView inputBarStyle]; if (style == JSInputBarStyleDefault) { self.textView = [[JSMessageTextView alloc] initWithFrame:CGRectMake(6.0f, 3.0f, width, height)]; self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.textView.backgroundColor = [UIColor redColor]; } else { self.textView = [[JSMessageTextView alloc] initWithFrame:CGRectMake(8.0f, 6.0f, width, height)]; self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.textView.backgroundColor = [UIColor clearColor]; self.textView.layer.borderColor = [[UIColor colorWithWhite:.8 alpha:1.0] CGColor]; self.textView.layer.borderWidth = 0.65f; self.textView.layer.cornerRadius = 6.0f; } // self.textView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f); // self.textView.keyboardAppearance = UIKeyboardAppearanceDefault; // self.textView.keyboardType = UIKeyboardTypeDefault; // self.textView.returnKeyType = UIReturnKeyDefault; // self.textView.scrollEnabled = YES; // self.textView.scrollsToTop = NO; // self.textView.userInteractionEnabled = YES; // self.textView.textColor = [UIColor blackColor]; // self.textView.font = [JSBubbleView font]; // self.textView.scrollIndicatorInsets = UIEdgeInsetsMake(10.0f, 0.0f, 10.0f, 0.0f); [self addSubview:self.textView]; if (style == JSInputBarStyleDefault) { UIImageView *inputFieldBack = [[UIImageView alloc] initWithFrame:CGRectMake(self.textView.frame.origin.x - 1.0f, 0.0f, self.textView.frame.size.width + 2.0f, self.frame.size.height)]; inputFieldBack.image = [UIImage inputField]; inputFieldBack.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); inputFieldBack.backgroundColor = [UIColor clearColor]; [self addSubview:inputFieldBack]; } } #pragma mark - Setters - (void)setSendButton:(UIButton *)btn { if(sendButton) [sendButton removeFromSuperview]; sendButton = btn; [self addSubview:self.sendButton]; } #pragma mark - Message input view - (void)adjustTextViewHeightBy:(CGFloat)changeInHeight { CGRect prevFrame = self.textView.frame; NSUInteger numLines = MAX([self.textView numberOfLinesOfText],[self.textView.text numberOfLines]); self.textView.frame = CGRectMake(prevFrame.origin.x, prevFrame.origin.y, prevFrame.size.width, prevFrame.size.height + changeInHeight); self.textView.contentInset = UIEdgeInsetsMake((numLines >= 6 ? 4.0f : 0.0f), 0.0f, (numLines >= 6 ? 4.0f : 0.0f), 0.0f); self.textView.scrollEnabled = (numLines >= 4); if(numLines >= 6) { CGPoint bottomOffset = CGPointMake(0.0f, self.textView.contentSize.height - self.textView.bounds.size.height); [self.textView setContentOffset:bottomOffset animated:YES]; } } + (CGFloat)textViewLineHeight { return 36.0f; // for fontSize 16.0f } + (CGFloat)maxLines { return 4.0f; } + (CGFloat)maxHeight { return ([JSMessageInputView maxLines] + 1.0f) * [JSMessageInputView textViewLineHeight]; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSMessageSoundEffect.h ================================================ // // JSMessageSoundEffect.h // // Created by Jesse Squires on 2/15/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import #import @interface JSMessageSoundEffect : NSObject + (void)playMessageReceivedSound; + (void)playMessageSentSound; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSMessageSoundEffect.m ================================================ // // JSMessageSoundEffect.m // // Created by Jesse Squires on 2/15/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "JSMessageSoundEffect.h" @interface JSMessageSoundEffect () + (void)playSoundWithName:(NSString *)name type:(NSString *)type; @end @implementation JSMessageSoundEffect + (void)playSoundWithName:(NSString *)name type:(NSString *)type { NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:type]; if([[NSFileManager defaultManager] fileExistsAtPath:path]) { SystemSoundID sound; AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath:path], &sound); AudioServicesPlaySystemSound(sound); } else { NSLog(@"Error: audio file not found at path: %@", path); } } + (void)playMessageReceivedSound { [JSMessageSoundEffect playSoundWithName:@"messageReceived" type:@"aiff"]; } + (void)playMessageSentSound { [JSMessageSoundEffect playSoundWithName:@"messageSent" type:@"aiff"]; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSMessageTextView.h ================================================ // // JSMessageTextView.h // PoGo // // Created by Yongchao on 20/11/13. // Copyright (c) 2013 Yongchao. All rights reserved. // #import "JSDismissiveTextView.h" @interface JSMessageTextView : JSDismissiveTextView /** * The text to be displayed when the text view is empty. The default value is `nil`. */ @property (copy, nonatomic) NSString *placeHolder; /** * The color of the place holder text. The default value is `[UIColor lightGrayColor]`. */ @property (strong, nonatomic) UIColor *placeHolderTextColor; /** * Returns an unsigned integer describing the number of lines of text contained in the text view. * * @return The number of lines of text in the text view. */ - (NSUInteger)numberOfLinesOfText; /** * Returns a constant describing the maximum number of characters that can fit on a single line of the text view. * * @return The maximum number of characters per line in the text view. */ + (NSUInteger)maxCharactersPerLine; /** * Returns an unsigned integer describing the number of lines necessary to display the given text in the text view. * * @param text The text to be displayed in the text view. * * @return The number of lines needed to display the given text. */ + (NSUInteger)numberOfLinesForMessage:(NSString *)text; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSMessageTextView.m ================================================ // // JSMessageTextView.m // PoGo // // Created by Yongchao on 20/11/13. // Copyright (c) 2013 Yongchao. All rights reserved. // #import "JSMessageTextView.h" #import "JSBubbleView.h" #import "NSString+JSMessagesView.h" @interface JSMessageTextView () - (void)setup; - (void)didReceiveTextDidChangeNotification:(NSNotification *)notification; @end @implementation JSMessageTextView #pragma mark - Initialization - (void)setup { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveTextDidChangeNotification:) name:UITextViewTextDidChangeNotification object:self]; _placeHolderTextColor = [UIColor lightGrayColor]; self.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.scrollIndicatorInsets = UIEdgeInsetsMake(10.0f, 0.0f, 10.0f, 8.0f); self.contentInset = UIEdgeInsetsZero; self.scrollEnabled = YES; self.scrollsToTop = NO; self.userInteractionEnabled = YES; self.font = [UIFont systemFontOfSize:16.0f]; self.textColor = [UIColor blackColor]; self.backgroundColor = [UIColor whiteColor]; self.keyboardAppearance = UIKeyboardAppearanceDefault; self.keyboardType = UIKeyboardTypeDefault; self.returnKeyType = UIReturnKeyDefault; self.textAlignment = NSTextAlignmentLeft; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if(self) { [self setup]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if(self) { [self setup]; } return self; } - (void)dealloc { _placeHolder = nil; _placeHolderTextColor = nil; [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self]; } #pragma mark - Setters - (void)setPlaceHolder:(NSString *)placeHolder { if([placeHolder isEqualToString:_placeHolder]) { return; } NSUInteger maxChars = [JSMessageTextView maxCharactersPerLine]; if([placeHolder length] > maxChars) { placeHolder = [placeHolder substringToIndex:maxChars - 8]; placeHolder = [[placeHolder trimWhitespace] stringByAppendingFormat:@"..."]; } _placeHolder = placeHolder; [self setNeedsDisplay]; } - (void)setPlaceHolderTextColor:(UIColor *)placeHolderTextColor { if([placeHolderTextColor isEqual:_placeHolderTextColor]) { return; } _placeHolderTextColor = placeHolderTextColor; [self setNeedsDisplay]; } #pragma mark - Message text view - (NSUInteger)numberOfLinesOfText { return [JSMessageTextView numberOfLinesForMessage:self.text]; } + (NSUInteger)maxCharactersPerLine { return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) ? 33 : 109; } + (NSUInteger)numberOfLinesForMessage:(NSString *)text { return (text.length / [JSMessageTextView maxCharactersPerLine]) + 1; } #pragma mark - Text view overrides - (void)setText:(NSString *)text { [super setText:text]; [self setNeedsDisplay]; } - (void)insertText:(NSString *)text { [super insertText:text]; [self setNeedsDisplay]; } - (void)setAttributedText:(NSAttributedString *)attributedText { [super setAttributedText:attributedText]; [self setNeedsDisplay]; } - (void)setContentInset:(UIEdgeInsets)contentInset { [super setContentInset:contentInset]; [self setNeedsDisplay]; } - (void)setFont:(UIFont *)font { [super setFont:font]; [self setNeedsDisplay]; } - (void)setTextAlignment:(NSTextAlignment)textAlignment { [super setTextAlignment:textAlignment]; [self setNeedsDisplay]; } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { [super drawRect:rect]; if([self.text length] == 0 && self.placeHolder) { CGRect placeHolderRect = CGRectMake(10.0f, 7.0f, rect.size.width, rect.size.height); [self.placeHolderTextColor set]; [self.placeHolder drawInRect:placeHolderRect withFont:self.font lineBreakMode:NSLineBreakByTruncatingTail alignment:self.textAlignment]; } } #pragma mark - Notifications - (void)didReceiveTextDidChangeNotification:(NSNotification *)notification { [self setNeedsDisplay]; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSMessagesViewController.h ================================================ // // JSMessagesViewController.h // // Created by Jesse Squires on 2/12/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // Largely based on work by Sam Soffes // https://github.com/soffes // // SSMessagesViewController // https://github.com/soffes/ssmessagesviewcontroller // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import #import "JSBubbleMessageCell.h" #import "JSMessageInputView.h" #import "JSMessageSoundEffect.h" #import "UIButton+JSMessagesView.h" #define kAllowsMedia YES typedef enum { JSMessagesViewTimestampPolicyAll = 0, JSMessagesViewTimestampPolicyAlternating, JSMessagesViewTimestampPolicyEveryThree, JSMessagesViewTimestampPolicyEveryFive, JSMessagesViewTimestampPolicyCustom } JSMessagesViewTimestampPolicy; typedef enum { JSMessagesViewAvatarPolicyIncomingOnly = 0, JSMessagesViewAvatarPolicyBoth, JSMessagesViewAvatarPolicyNone } JSMessagesViewAvatarPolicy; @protocol JSMessagesViewDelegate @required - (void)sendPressed:(UIButton *)sender withText:(NSString *)text; - (void)cameraPressed:(id)sender; - (JSBubbleMessageType)messageTypeForRowAtIndexPath:(NSIndexPath *)indexPath; - (JSBubbleMessageStyle)messageStyleForRowAtIndexPath:(NSIndexPath *)indexPath; - (JSBubbleMediaType)messageMediaTypeForRowAtIndexPath:(NSIndexPath *)indexPath; - (JSMessagesViewTimestampPolicy)timestampPolicy; - (JSMessagesViewAvatarPolicy)avatarPolicy; - (JSAvatarStyle)avatarStyle; @optional - (BOOL)hasTimestampForRowAtIndexPath:(NSIndexPath *)indexPath; @end @protocol JSMessagesViewDataSource @required - (NSString *)textForRowAtIndexPath:(NSIndexPath *)indexPath; - (NSDate *)timestampForRowAtIndexPath:(NSIndexPath *)indexPath; - (UIImage *)avatarImageForIncomingMessage; - (UIImage *)avatarImageForOutgoingMessage; - (SEL)avatarImageForIncomingMessageAction; - (SEL)avatarImageForOutgoingMessageAction; @optional - (id)dataForRowAtIndexPath:(NSIndexPath *)indexPath; @end @interface JSMessagesViewController : UIViewController @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; @property (strong, nonatomic) UITableView *tableView; @property (strong, nonatomic) JSMessageInputView *inputToolBarView; @property (assign, nonatomic) CGFloat previousTextViewContentHeight; @property (assign, nonatomic, readonly) UIEdgeInsets originalTableViewContentInset; @property (nonatomic,strong) NSMutableArray *selectedMarks; #pragma mark - Initialization - (UIButton *)sendButton; #pragma mark - Actions - (void)sendPressed:(UIButton *)sender; #pragma mark - Messages view controller - (BOOL)shouldHaveTimestampForRowAtIndexPath:(NSIndexPath *)indexPath; - (BOOL)shouldHaveAvatarForRowAtIndexPath:(NSIndexPath *)indexPath; - (void)finishSend:(BOOL)isMedia; - (void)setBackgroundColor:(UIColor *)color; - (void)scrollToBottomAnimated:(BOOL)animated; #pragma mark - Keyboard notifications - (void)handleWillShowKeyboard:(NSNotification *)notification; - (void)handleWillHideKeyboard:(NSNotification *)notification; - (void)keyboardWillShowHide:(NSNotification *)notification; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/JSMessagesViewController.m ================================================ // // JSMessagesViewController.m // // Created by Jesse Squires on 2/12/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // Largely based on work by Sam Soffes // https://github.com/soffes // // SSMessagesViewController // https://github.com/soffes/ssmessagesviewcontroller // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "JSMessagesViewController.h" #import "NSString+JSMessagesView.h" #import "UIView+AnimationOptionsForCurve.h" #import "UIColor+JSMessagesView.h" #import "JSDismissiveTextView.h" #define OSVersionIsAtLeastiOS7 (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) #define INPUT_HEIGHT 46.0f @interface JSMessagesViewController () - (void)setup; @end @implementation JSMessagesViewController - (void)loadView { [super loadView]; [self fixFrame]; } - (void)fixFrame { CGRect frame = [UIScreen mainScreen].bounds; if (OSVersionIsAtLeastiOS7 == YES) { self.edgesForExtendedLayout = UIRectEdgeNone; }else{ frame.size.height -= 20 + 44; } self.view.frame = frame; self.view.bounds = frame; } #pragma mark - Initialization - (void)setup { if([self.view isKindOfClass:[UIScrollView class]]) { // fix for ipad modal form presentations ((UIScrollView *)self.view).scrollEnabled = NO; } CGSize size = self.view.frame.size; CGRect tableFrame = CGRectMake(0.0f, 0.0f, size.width, size.height - INPUT_HEIGHT); self.tableView = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStylePlain]; self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.tableView.dataSource = self; self.tableView.delegate = self; [self.view addSubview:self.tableView]; UIButton* mediaButton = nil; if (kAllowsMedia) { // set up the image and button frame UIImage* image = [UIImage imageNamed:@"PhotoIcon"]; CGRect frame = CGRectMake(4, 0, image.size.width, image.size.height); CGFloat yHeight = (INPUT_HEIGHT - frame.size.height) / 2.0f; frame.origin.y = yHeight; // make the button mediaButton = [[UIButton alloc] initWithFrame:frame]; [mediaButton setBackgroundImage:image forState:UIControlStateNormal]; // button action [mediaButton addTarget:self action:@selector(cameraAction:) forControlEvents:UIControlEventTouchUpInside]; } CGRect inputFrame = CGRectMake(0.0f, size.height - INPUT_HEIGHT, size.width, INPUT_HEIGHT); self.inputToolBarView = [[JSMessageInputView alloc] initWithFrame:inputFrame delegate:self]; // TODO: refactor self.inputToolBarView.textView.dismissivePanGestureRecognizer = self.tableView.panGestureRecognizer; self.inputToolBarView.textView.keyboardDelegate = self; self.inputToolBarView.textView.placeHolder = @"说点什么呢?"; UIButton *sendButton = [self sendButton]; sendButton.enabled = NO; sendButton.frame = CGRectMake(self.inputToolBarView.frame.size.width - 65.0f, 8.0f, 59.0f, 26.0f); [sendButton addTarget:self action:@selector(sendPressed:) forControlEvents:UIControlEventTouchUpInside]; [self.inputToolBarView setSendButton:sendButton]; [self.view addSubview:self.inputToolBarView]; if (kAllowsMedia) { // adjust the size of the send button to balance out more with the camera button on the other side. CGRect frame = self.inputToolBarView.sendButton.frame; frame.size.width -= 16; frame.origin.x += 16; self.inputToolBarView.sendButton.frame = frame; // add the camera button [self.inputToolBarView addSubview:mediaButton]; // move the tet view over frame = self.inputToolBarView.textView.frame; frame.origin.x += mediaButton.frame.size.width + mediaButton.frame.origin.x; frame.size.width -= mediaButton.frame.size.width + mediaButton.frame.origin.x; frame.size.width += 16; // from the send button adjustment above self.inputToolBarView.textView.frame = frame; } self.selectedMarks = [NSMutableArray new]; [self setBackgroundColor:[UIColor messagesBackgroundColor]]; } - (UIButton *)sendButton { return [UIButton defaultSendButton]; } #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; [self setup]; } - (void)viewWillAppear:(BOOL)animated { [self scrollToBottomAnimated:NO]; _originalTableViewContentInset = self.tableView.contentInset; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleWillShowKeyboard:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleWillHideKeyboard:) name:UIKeyboardWillHideNotification object:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.inputToolBarView resignFirstResponder]; [self setEditing:NO animated:YES]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; NSLog(@"*** %@: didReceiveMemoryWarning ***", self.class); } - (void)dealloc { self.delegate = nil; self.dataSource = nil; self.tableView = nil; self.inputToolBarView = nil; } #pragma mark - View rotation - (BOOL)shouldAutorotate { return NO; } - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; [self.tableView reloadData]; [self.tableView setNeedsLayout]; } #pragma mark - Actions - (void)sendPressed:(UIButton *)sender { [self.delegate sendPressed:sender withText:[self.inputToolBarView.textView.text trimWhitespace]]; } - (void)cameraAction:(id)sender { if(self.delegate && [self.delegate respondsToSelector:@selector(cameraPressed:)]){ [self.delegate cameraPressed:sender]; } } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 0; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { JSBubbleMessageType type = [self.delegate messageTypeForRowAtIndexPath:indexPath]; JSBubbleMessageStyle bubbleStyle = [self.delegate messageStyleForRowAtIndexPath:indexPath]; JSBubbleMediaType mediaType = [self.delegate messageMediaTypeForRowAtIndexPath:indexPath]; JSAvatarStyle avatarStyle = [self.delegate avatarStyle]; BOOL hasTimestamp = [self shouldHaveTimestampForRowAtIndexPath:indexPath]; BOOL hasAvatar = [self shouldHaveAvatarForRowAtIndexPath:indexPath]; NSInteger row = indexPath.row; NSString *CellID = [NSString stringWithFormat:@"MessageCell_%ld_%d_%d_%d_%ld", type, bubbleStyle, hasTimestamp, hasAvatar, (long)row]; JSBubbleMessageCell *cell = (JSBubbleMessageCell *)[tableView dequeueReusableCellWithIdentifier:CellID]; if(!cell) cell = [[JSBubbleMessageCell alloc] initWithBubbleType:type bubbleStyle:bubbleStyle avatarStyle:(hasAvatar) ? avatarStyle : JSAvatarStyleNone mediaType:mediaType hasTimestamp:hasTimestamp reuseIdentifier:CellID]; if(hasTimestamp) [cell setTimestamp:[self.dataSource timestampForRowAtIndexPath:indexPath]]; if(hasAvatar) { switch (type) { case JSBubbleMessageTypeIncoming: [cell setAvatarImage:[self.dataSource avatarImageForIncomingMessage]]; [cell setAvatarImageTarget:self.dataSource action:[self.dataSource avatarImageForIncomingMessageAction]]; break; case JSBubbleMessageTypeOutgoing: [cell setAvatarImage:[self.dataSource avatarImageForOutgoingMessage]]; [cell setAvatarImageTarget:self.dataSource action:[self.dataSource avatarImageForOutgoingMessageAction]]; break; } } if (kAllowsMedia) [cell setMedia:[self.dataSource dataForRowAtIndexPath:indexPath]]; [cell setMessage:[self.dataSource textForRowAtIndexPath:indexPath]]; [cell setBackgroundColor:tableView.backgroundColor]; cell.isSelected = [self.selectedMarks containsObject:CellID] ? YES : NO; return cell; } -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ JSBubbleMessageType type = [self.delegate messageTypeForRowAtIndexPath:indexPath]; JSBubbleMessageStyle bubbleStyle = [self.delegate messageStyleForRowAtIndexPath:indexPath]; BOOL hasTimestamp = [self shouldHaveTimestampForRowAtIndexPath:indexPath]; BOOL hasAvatar = [self shouldHaveAvatarForRowAtIndexPath:indexPath]; NSInteger row = indexPath.row; NSString *CellID = [NSString stringWithFormat:@"MessageCell_%ld_%d_%d_%d_%ld", type, bubbleStyle, hasTimestamp, hasAvatar, (long)row]; if ([self.selectedMarks containsObject:CellID])// Is selected? [self.selectedMarks removeObject:CellID]; else [self.selectedMarks addObject:CellID]; [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } #pragma mark - Table view delegate - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if(![self.delegate messageMediaTypeForRowAtIndexPath:indexPath]){ return [JSBubbleMessageCell neededHeightForText:[self.dataSource textForRowAtIndexPath:indexPath] timestamp:[self shouldHaveTimestampForRowAtIndexPath:indexPath] avatar:[self shouldHaveAvatarForRowAtIndexPath:indexPath]]; }else{ return [JSBubbleMessageCell neededHeightForImage:[self.dataSource dataForRowAtIndexPath:indexPath] timestamp:[self shouldHaveTimestampForRowAtIndexPath:indexPath] avatar:[self shouldHaveAvatarForRowAtIndexPath:indexPath]]; } } #pragma mark - Messages view controller - (BOOL)shouldHaveTimestampForRowAtIndexPath:(NSIndexPath *)indexPath { switch ([self.delegate timestampPolicy]) { case JSMessagesViewTimestampPolicyAll: return YES; case JSMessagesViewTimestampPolicyAlternating: return indexPath.row % 2 == 0; case JSMessagesViewTimestampPolicyEveryThree: return indexPath.row % 3 == 0; case JSMessagesViewTimestampPolicyEveryFive: return indexPath.row % 5 == 0; case JSMessagesViewTimestampPolicyCustom: if([self.delegate respondsToSelector:@selector(hasTimestampForRowAtIndexPath:)]) return [self.delegate hasTimestampForRowAtIndexPath:indexPath]; default: return NO; } } - (BOOL)shouldHaveAvatarForRowAtIndexPath:(NSIndexPath *)indexPath { switch ([self.delegate avatarPolicy]) { case JSMessagesViewAvatarPolicyIncomingOnly: return [self.delegate messageTypeForRowAtIndexPath:indexPath] == JSBubbleMessageTypeIncoming; case JSMessagesViewAvatarPolicyBoth: return YES; case JSMessagesViewAvatarPolicyNone: default: return NO; } } - (void)finishSend:(BOOL)isMedia { if (!isMedia) { [self.inputToolBarView.textView setText:nil]; [self textViewDidChange:self.inputToolBarView.textView]; } [self.tableView reloadData]; [self scrollToBottomAnimated:YES]; } - (void)setBackgroundColor:(UIColor *)color { self.view.backgroundColor = color; self.tableView.backgroundColor = color; self.tableView.separatorColor = color; } - (void)scrollToBottomAnimated:(BOOL)animated { NSInteger rows = [self.tableView numberOfRowsInSection:0]; if(rows > 0) { [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated]; } } - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)position animated:(BOOL)animated { [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:position animated:animated]; } #pragma mark - Text view delegate - (void)textViewDidBeginEditing:(UITextView *)textView { [textView becomeFirstResponder]; if(!self.previousTextViewContentHeight) self.previousTextViewContentHeight = textView.contentSize.height; [self scrollToBottomAnimated:YES]; } - (void)textViewDidEndEditing:(UITextView *)textView { [textView resignFirstResponder]; } - (void)textViewDidChange:(UITextView *)textView { CGFloat maxHeight = [JSMessageInputView maxHeight]; CGSize size = [textView sizeThatFits:CGSizeMake(textView.frame.size.width, maxHeight)]; CGFloat textViewContentHeight = size.height; // End of textView.contentSize replacement code BOOL isShrinking = textViewContentHeight < self.previousTextViewContentHeight; CGFloat changeInHeight = textViewContentHeight - self.previousTextViewContentHeight; if(!isShrinking && self.previousTextViewContentHeight == maxHeight) { changeInHeight = 0; } else { changeInHeight = MIN(changeInHeight, maxHeight - self.previousTextViewContentHeight); } if(changeInHeight != 0.0f) { // if(!isShrinking) // [self.inputToolBarView adjustTextViewHeightBy:changeInHeight]; [UIView animateWithDuration:0.25f animations:^{ UIEdgeInsets insets = UIEdgeInsetsMake(0.0f, 0.0f, self.tableView.contentInset.bottom + changeInHeight, 0.0f); self.tableView.contentInset = insets; self.tableView.scrollIndicatorInsets = insets; [self scrollToBottomAnimated:NO]; if(isShrinking) { // if shrinking the view, animate text view frame BEFORE input view frame [self.inputToolBarView adjustTextViewHeightBy:changeInHeight]; } CGRect inputViewFrame = self.inputToolBarView.frame; self.inputToolBarView.frame = CGRectMake(0.0f, inputViewFrame.origin.y - changeInHeight, inputViewFrame.size.width, inputViewFrame.size.height + changeInHeight); if(!isShrinking) { [self.inputToolBarView adjustTextViewHeightBy:changeInHeight]; } } completion:^(BOOL finished) { }]; self.previousTextViewContentHeight = MIN(textViewContentHeight, maxHeight); } self.inputToolBarView.sendButton.enabled = ([textView.text trimWhitespace].length > 0); } #pragma mark - Keyboard notifications - (void)handleWillShowKeyboard:(NSNotification *)notification { [self keyboardWillShowHide:notification]; } - (void)handleWillHideKeyboard:(NSNotification *)notification { [self keyboardWillShowHide:notification]; } - (void)keyboardWillShowHide:(NSNotification *)notification { CGRect keyboardRect = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; UIViewAnimationCurve curve = [[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; double duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; [UIView animateWithDuration:duration delay:0.0f options:[UIView animationOptionsForCurve:curve] animations:^{ CGFloat keyboardY = [self.view convertRect:keyboardRect fromView:nil].origin.y; CGRect inputViewFrame = self.inputToolBarView.frame; CGFloat inputViewFrameY = keyboardY - inputViewFrame.size.height; // for ipad modal form presentations CGFloat messageViewFrameBottom = self.view.frame.size.height - INPUT_HEIGHT; if(inputViewFrameY > messageViewFrameBottom) inputViewFrameY = messageViewFrameBottom; self.inputToolBarView.frame = CGRectMake(inputViewFrame.origin.x, inputViewFrameY, inputViewFrame.size.width, inputViewFrame.size.height); UIEdgeInsets insets = self.originalTableViewContentInset; insets.bottom = self.view.frame.size.height - self.inputToolBarView.frame.origin.y - inputViewFrame.size.height; self.tableView.contentInset = insets; self.tableView.scrollIndicatorInsets = insets; } completion:^(BOOL finished) { }]; } #pragma mark - Dismissive text view delegate - (void)keyboardDidScrollToPoint:(CGPoint)pt { CGRect inputViewFrame = self.inputToolBarView.frame; CGPoint keyboardOrigin = [self.view convertPoint:pt fromView:nil]; inputViewFrame.origin.y = keyboardOrigin.y - inputViewFrame.size.height; self.inputToolBarView.frame = inputViewFrame; } - (void)keyboardWillBeDismissed { CGRect inputViewFrame = self.inputToolBarView.frame; inputViewFrame.origin.y = self.view.bounds.size.height - inputViewFrame.size.height; self.inputToolBarView.frame = inputViewFrame; } //- (void)keyboardWillSnapBackToPoint:(CGPoint)pt //{ // CGRect inputViewFrame = self.inputToolBarView.frame; // CGPoint keyboardOrigin = [self.view convertPoint:pt fromView:nil]; // inputViewFrame.origin.y = keyboardOrigin.y - inputViewFrame.size.height; // self.inputToolBarView.frame = inputViewFrame; // // //} @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/NSString+JSMessagesView.h ================================================ // // NSString+JSMessagesView.h // // Created by Jesse Squires on 2/14/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import @interface NSString (JSMessagesView) - (NSString *)trimWhitespace; - (NSUInteger)numberOfLines; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/NSString+JSMessagesView.m ================================================ // // NSString+JSMessagesView.m // // Created by Jesse Squires on 2/14/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "NSString+JSMessagesView.h" @implementation NSString (JSMessagesView) - (NSString *)trimWhitespace { return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } - (NSUInteger)numberOfLines { return [self componentsSeparatedByString:@"\n"].count + 1; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/UIButton+JSMessagesView.h ================================================ // // UIButton+JSMessagesView.h // MessagesDemo // // Created by Jesse Squires on 3/24/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // #import @interface UIButton (JSMessagesView) + (UIButton *)defaultSendButton; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/UIButton+JSMessagesView.m ================================================ // // UIButton+JSMessagesView.m // MessagesDemo // // Created by Jesse Squires on 3/24/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // #import "UIButton+JSMessagesView.h" #import "JSMessageInputView.h" @implementation UIButton (JSMessagesView) + (UIButton *)defaultSendButton { UIButton *sendButton; if ([JSMessageInputView inputBarStyle] == JSInputBarStyleFlat) { sendButton = [UIButton buttonWithType:UIButtonTypeSystem]; } else { sendButton = [UIButton buttonWithType:UIButtonTypeCustom]; // UIEdgeInsets insets = UIEdgeInsetsMake(0.0f, 13.0f, 0.0f, 13.0f); // UIImage *sendBack = [[UIImage imageNamed:@"send"] resizableImageWithCapInsets:insets]; // UIImage *sendBackHighLighted = [[UIImage imageNamed:@"send"] resizableImageWithCapInsets:insets]; UIImage *sendBack = [UIImage imageNamed:@"send.png"]; UIImage *sendBackHighLighted = [UIImage imageNamed:@"send.png"]; [sendButton setBackgroundImage:sendBack forState:UIControlStateNormal]; [sendButton setBackgroundImage:sendBack forState:UIControlStateDisabled]; [sendButton setBackgroundImage:sendBackHighLighted forState:UIControlStateHighlighted]; sendButton.titleLabel.font = [UIFont boldSystemFontOfSize:16.0f]; UIColor *titleShadow = UIColorFromRGB(0x333435); [sendButton setTitleShadowColor:titleShadow forState:UIControlStateNormal]; [sendButton setTitleShadowColor:titleShadow forState:UIControlStateHighlighted]; sendButton.titleLabel.shadowOffset = CGSizeMake(0.0f, -1.0f); [sendButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [sendButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; [sendButton setTitleColor:[UIColor colorWithWhite:1.0f alpha:0.5f] forState:UIControlStateDisabled]; } NSString *title = NSLocalizedString(@"Send", @"发送"); [sendButton setTitle:title forState:UIControlStateNormal]; [sendButton setTitle:title forState:UIControlStateHighlighted]; [sendButton setTitle:title forState:UIControlStateDisabled]; sendButton.autoresizingMask = (UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin); return sendButton; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/UIColor+JSMessagesView.h ================================================ // // UIColor+JSMessagesView.h // // Created by Jesse Squires on 3/19/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import @interface UIColor (JSMessagesView) + (UIColor *)messagesBackgroundColor; + (UIColor *)messagesTimestampColor; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/UIColor+JSMessagesView.m ================================================ // // UIColor+JSMessagesView.m // // Created by Jesse Squires on 3/19/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "UIColor+JSMessagesView.h" #import "JSMessageInputView.h" @implementation UIColor (JSMessagesView) + (UIColor *)messagesBackgroundColor { if ([JSMessageInputView inputBarStyle] == JSInputBarStyleFlat) return [UIColor whiteColor]; return [UIColor colorWithRed:0.859f green:0.886f blue:0.929f alpha:1.0f]; } + (UIColor *)messagesTimestampColor { return [UIColor colorWithRed:0.533f green:0.573f blue:0.647f alpha:1.0f]; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/UIImage+JSMessagesView.h ================================================ // // UIImage+JSMessagesView.h // // Created by Jesse Squires on 7/25/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import @interface UIImage (JSMessagesView) #pragma mark - Avatar styles - (UIImage *)circleImageWithSize:(CGFloat)size; - (UIImage *)squareImageWithSize:(CGFloat)size; - (UIImage *)imageAsCircle:(BOOL)clipToCircle withDiamter:(CGFloat)diameter borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth shadowOffSet:(CGSize)shadowOffset; #pragma mark - Input bar + (UIImage *)inputBar; + (UIImage *)inputField; #pragma mark - Bubble cap insets - (UIImage *)makeStretchableDefaultIncoming; - (UIImage *)makeStretchableDefaultOutgoing; - (UIImage *)makeStretchableSquareIncoming; - (UIImage *)makeStretchableSquareOutgoing; - (UIImage *)makeStretchableFlatIncoming; - (UIImage *)makeStretchableFlatOutgoing; #pragma mark - Incoming message bubbles + (UIImage *)bubbleDefaultIncoming; + (UIImage *)bubbleDefaultIncomingSelected; + (UIImage *)bubbleDefaultIncomingGreen; + (UIImage *)bubbleSquareIncoming; + (UIImage *)bubbleSquareIncomingSelected; + (UIImage *)bubbleFlatIncoming; + (UIImage *)bubbleFlatIncomingSelected; #pragma mark - Outgoing message bubbles + (UIImage *)bubbleDefaultOutgoing; + (UIImage *)bubbleDefaultOutgoingSelected; + (UIImage *)bubbleDefaultOutgoingGreen; + (UIImage *)bubbleSquareOutgoing; + (UIImage *)bubbleSquareOutgoingSelected; + (UIImage *)bubbleFlatOutgoing; + (UIImage *)bubbleFlatOutgoingSelected; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/UIImage+JSMessagesView.m ================================================ // // UIImage+JSMessagesView.m // // Created by Jesse Squires on 7/25/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "UIImage+JSMessagesView.h" #import "JSMessageInputView.h" @implementation UIImage (JSMessagesView) #pragma mark - Avatar styles - (UIImage *)circleImageWithSize:(CGFloat)size { return [self imageAsCircle:YES withDiamter:size borderColor:[UIColor colorWithHue:0.0f saturation:0.0f brightness:0.8f alpha:1.0f] borderWidth:1.0f shadowOffSet:CGSizeMake(0.0f, 1.0f)]; } - (UIImage *)squareImageWithSize:(CGFloat)size { return [self imageAsCircle:NO withDiamter:size borderColor:[UIColor colorWithHue:0.0f saturation:0.0f brightness:0.8f alpha:1.0f] borderWidth:1.0f shadowOffSet:CGSizeMake(0.0f, 1.0f)]; } - (UIImage *)imageAsCircle:(BOOL)clipToCircle withDiamter:(CGFloat)diameter borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth shadowOffSet:(CGSize)shadowOffset { // increase given size for border and shadow CGFloat increase = diameter * 0.15f; CGFloat newSize = diameter + increase; CGRect newRect = CGRectMake(0.0f, 0.0f, newSize, newSize); // fit image inside border and shadow CGRect imgRect = CGRectMake(increase, increase, newRect.size.width - (increase * 2.0f), newRect.size.height - (increase * 2.0f)); UIGraphicsBeginImageContextWithOptions(newRect.size, NO, [UIScreen mainScreen].scale); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); // draw shadow if(!CGSizeEqualToSize(shadowOffset, CGSizeZero)) CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset.width, shadowOffset.height), 3.0f, [UIColor colorWithWhite:0.0f alpha:0.45f].CGColor); // draw border // as circle or square CGPathRef borderPath = (clipToCircle) ? CGPathCreateWithEllipseInRect(imgRect, NULL) : CGPathCreateWithRect(imgRect, NULL); CGContextSetStrokeColorWithColor(context, borderColor.CGColor); CGContextSetLineWidth(context, borderWidth); CGContextAddPath(context, borderPath); CGContextDrawPath(context, kCGPathFillStroke); CGPathRelease(borderPath); CGContextRestoreGState(context); // clip to circle if(clipToCircle) { UIBezierPath *imgPath = [UIBezierPath bezierPathWithOvalInRect:imgRect]; [imgPath addClip]; } [self drawInRect:imgRect]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; } #pragma mark - Input bar + (UIImage *)inputBar { if ([JSMessageInputView inputBarStyle] == JSInputBarStyleFlat) return [UIImage imageNamed:@"input-bar-flat"]; else // jSInputBarStyleDefault return [[UIImage imageNamed:@"input-bar"] resizableImageWithCapInsets:UIEdgeInsetsMake(19.0f, 3.0f, 19.0f, 3.0f)]; } + (UIImage *)inputField { if ([JSMessageInputView inputBarStyle] == JSInputBarStyleFlat) return nil; // no graphic around input field. else // jSInputBarStyleDefault return [[UIImage imageNamed:@"input-field"] resizableImageWithCapInsets:UIEdgeInsetsMake(20.0f, 12.0f, 18.0f, 18.0f)]; } #pragma mark - Bubble cap insets - (UIImage *)makeStretchableDefaultIncoming { return [self resizableImageWithCapInsets:UIEdgeInsetsMake(15.0f, 20.0f, 15.0f, 20.0f) resizingMode:UIImageResizingModeStretch]; } - (UIImage *)makeStretchableDefaultOutgoing { return [self resizableImageWithCapInsets:UIEdgeInsetsMake(15.0f, 20.0f, 15.0f, 20.0f) resizingMode:UIImageResizingModeStretch]; } - (UIImage *)makeStretchableSquareIncoming { return [self resizableImageWithCapInsets:UIEdgeInsetsMake(15.0f, 25.0f, 16.0f, 23.0f) resizingMode:UIImageResizingModeStretch]; } - (UIImage *)makeStretchableSquareOutgoing { return [self resizableImageWithCapInsets:UIEdgeInsetsMake(15.0f, 18.0f, 16.0f, 23.0f) resizingMode:UIImageResizingModeStretch]; } // JeremyStone - (UIImage *)makeStretchableFlatIncoming { return [self resizableImageWithCapInsets:UIEdgeInsetsMake(15.0f, 20.0f, 15.0f, 20.0f) resizingMode:UIImageResizingModeStretch]; } // JeremyStone - (UIImage *)makeStretchableFlatOutgoing { return [self resizableImageWithCapInsets:UIEdgeInsetsMake(14.0f, 15.0f, 14.0f, 20.0f) resizingMode:UIImageResizingModeStretch]; } #pragma mark - Incoming message bubbles + (UIImage *)bubbleDefaultIncoming { return [[UIImage imageNamed:@"bubble-default-incoming"] makeStretchableDefaultIncoming]; } + (UIImage *)bubbleDefaultIncomingSelected { return [[UIImage imageNamed:@"bubble-default-incoming-selected"] makeStretchableDefaultIncoming]; } + (UIImage *)bubbleDefaultIncomingGreen { return [[UIImage imageNamed:@"bubble-default-incoming-green"] makeStretchableDefaultIncoming]; } + (UIImage *)bubbleSquareIncoming { return [[UIImage imageNamed:@"bubble-square-incoming"] makeStretchableSquareIncoming]; } + (UIImage *)bubbleSquareIncomingSelected { return [[UIImage imageNamed:@"bubble-square-incoming-selected"] makeStretchableSquareIncoming]; } // JeremyStone + (UIImage *)bubbleFlatIncoming { return [[UIImage imageNamed:@"bubble-flat-incoming"] makeStretchableFlatIncoming]; } // JeremyStone + (UIImage *)bubbleFlatIncomingSelected { return [[UIImage imageNamed:@"bubble-flat-incoming-selected"] makeStretchableFlatIncoming]; } #pragma mark - Outgoing message bubbles + (UIImage *)bubbleDefaultOutgoing { return [[UIImage imageNamed:@"bubble-default-outgoing"] makeStretchableDefaultOutgoing]; } + (UIImage *)bubbleDefaultOutgoingSelected { return [[UIImage imageNamed:@"bubble-default-outgoing-selected"] makeStretchableDefaultOutgoing]; } + (UIImage *)bubbleDefaultOutgoingGreen { return [[UIImage imageNamed:@"bubble-default-outgoing-green"] makeStretchableDefaultOutgoing]; } + (UIImage *)bubbleSquareOutgoing { return [[UIImage imageNamed:@"bubble-square-outgoing"] makeStretchableSquareOutgoing]; } + (UIImage *)bubbleSquareOutgoingSelected { return [[UIImage imageNamed:@"bubble-square-outgoing-selected"] makeStretchableSquareOutgoing]; } // JeremyStone + (UIImage *)bubbleFlatOutgoing { return [[UIImage imageNamed:@"bubble-flat-outgoing-selected"] makeStretchableFlatOutgoing]; } // JeremyStone + (UIImage *)bubbleFlatOutgoingSelected { return [[UIImage imageNamed:@"bubble-flat-outgoing"] makeStretchableFlatOutgoing]; } @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/UIView+AnimationOptionsForCurve.h ================================================ // // UIView+AnimationOptionsForCurve.h // // Created by Jesse Squires on 3/11/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import @interface UIView (AnimationOptionsForCurve) + (UIViewAnimationOptions)animationOptionsForCurve:(UIViewAnimationCurve)curve; @end ================================================ FILE: ChatMessageTableViewController/MessagesTableViewController/UIView+AnimationOptionsForCurve.m ================================================ // // UIView+AnimationOptionsForCurve.m // // Created by Jesse Squires on 3/11/13. // Copyright (c) 2013 Hexed Bits. All rights reserved. // // http://www.hexedbits.com // // // The MIT License // Copyright (c) 2013 Jesse Squires // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #import "UIView+AnimationOptionsForCurve.h" @implementation UIView (AnimationOptionsForCurve) + (UIViewAnimationOptions)animationOptionsForCurve:(UIViewAnimationCurve)curve { switch (curve) { case UIViewAnimationCurveEaseInOut: return UIViewAnimationOptionCurveEaseInOut; break; case UIViewAnimationCurveEaseIn: return UIViewAnimationOptionCurveEaseIn; break; case UIViewAnimationCurveEaseOut: return UIViewAnimationOptionCurveEaseOut; break; case UIViewAnimationCurveLinear: return UIViewAnimationOptionCurveLinear; break; } return kNilOptions; } @end ================================================ FILE: README.md ================================================ # ChatMessageTableViewController A messages UI for iPhone. ChatMessageTableViewController is like iMessage app. support text && image . ![Messages Screenshot 1][img1] ![Messages Screenshot 2][img2] This messages tableview controller is very similar to the one in the iOS Messages app. **Note, this is only a messaging UI, not a messaging app.** This is intended to be used in an existing app where you have (or are developing) a messaging system and need a user interface for it. **See more [screenshots][link1] in the `Screenshots/` directory. (Surprise!)** ## About This is based on work by [@soffes](http://github.com/soffes) [SSMessagingViewController][ss]. I developed this to use in [Hemoglobe](http://www.hemoglobe.com) for private messages between users. [Square message bubbles]([img4]) designed by [@michaelschultz](http://www.twitter.com/michaelschultz). ## Features * Up-to-date for iOS 6.0 and ARC (iOS 5.0+ required) * Universal for iPhone * Allows arbitrary message (and bubble) sizes * Copy & paste text message && Save image message * Timestamp options * Swipe/pull down to hide keyboard * Dynamically resizes input text view as you type * Smooth hiding/showing keyboard animations with `NSNotification` * Automatically enables/disables send button if text view is empty or not * Smooth send animations * Send/Receive sound effects * Various bubble styles ### From source * Drag the `JSMessagesTableViewController/` folder to your project. * Add the `AudioToolbox.framework` to your project, if you want to use the sound effects ## How To Use #####Subclass `JSMessagesViewController` * In `- (void)viewDidLoad` * Set your view controller as the `delegate` and `datasource` * Set your view controller `title` #####Implement the `JSMessagesViewDelegate` protocol ````objective-c - (void)sendPressed:(UIButton *)sender withText:(NSString *)text ```` * Hook into your own backend here * Call `[self finishSend]` at the end of this method to animate and reset the text input view * Optionally play sound effects * For outgoing messages `[JSMessageSoundEffect playMessageSentSound]` * For incoming messages `[JSMessageSoundEffect playMessageReceivedSound]` ````objective-c - (JSBubbleMessageType)messageTypeForRowAtIndexPath:(NSIndexPath *)indexPath ```` * The type of bubble for this row, options are: * `JSBubbleMessageTypeIncoming` * `JSBubbleMessageTypeOutgoing` * The type of Message for this row, options are: * `JSBubbleMediaTypeText` * `JSBubbleMediaTypeImage` ````objective-c - (JSBubbleMediaType)messageMediaTypeForRowAtIndexPath:(NSIndexPath *)indexPath ```` * The [style of the bubble][link1] for this row, options are: * `JSBubbleMessageStyleDefault` * `JSBubbleMessageStyleSquare` * `JSBubbleMessageStyleDefaultGreen` ````objective-c - (JSMessagesViewTimestampPolicy)timestampPolicy ```` * How/when to display timestamps for messages, options are: * `JSMessagesViewTimestampPolicyAll` * `JSMessagesViewTimestampPolicyAlternating` * `JSMessagesViewTimestampPolicyEveryThree` * `JSMessagesViewTimestampPolicyEveryFive` * `JSMessagesViewTimestampPolicyCustom` ````objective-c - (JSMessagesViewAvatarPolicy)avatarPolicy ```` * How/when to display avatars, options are: * `JSMessagesViewAvatarPolicyIncomingOnly` * `JSMessagesViewAvatarPolicyBoth` * `JSMessagesViewAvatarPolicyNone` ````objective-c - (JSAvatarStyle)avatarStyle ```` * The [style for the avatars][link1], options are: * `JSAvatarStyleCircle` * `JSAvatarStyleSquare` * `JSAvatarStyleNone` ````objective-c - (BOOL)hasTimestampForRowAtIndexPath:(NSIndexPath *)indexPath ```` * Returns if this row should display a timestamp or not * Required only if using `JSMessagesViewTimestampPolicyCustom` #####Implement the `JSMessagesViewDataSource` protocol ````objective-c - (NSString *)textForRowAtIndexPath:(NSIndexPath *)indexPath ```` * The text to be displayed for this row ````objective-c - (NSDate *)timestampForRowAtIndexPath:(NSIndexPath *)indexPath ```` * The timestamp to be displayed *above* this row ````objective-c - (UIImage *)avatarImageForIncomingMessage ```` * The avatar image for incoming messages ````objective-c - (UIImage *)avatarImageForOutgoingMessage ```` * The avatar image for outgoing messages #####Implement the [table view data source][ref1] method that you should be familiar with ````objective-c - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section ```` #####Customize * For custom background color, use `- (void)setBackgroundColor:(UIColor *)color` * For custom send button, override `- (UIButton *)sendButton` ##### Notes * You may present view programmatically, or use Storyboards * Your `JSMessagesViewController` subclass **must** be presented in a `UINavigationController` ##### Demo projects included * `MessagesDemo.xcodeproj` for example of programmatic presentation * `MessagesDemoStoryboards/MessagesDemoSB.xcodeproj` for example of use with Storyboards ## Apps Using This Control [Hemoglobe](http://bit.ly/hemoglobeapp) [FourChat](https://itunes.apple.com/us/app/fourchat/id650833730?mt=8) [Libraries for developers](https://itunes.apple.com/us/app/libraries-for-developers/id653427112?mt=8) [jessesquires 's MessagesTableViewController](https://github.com/jessesquires/MessagesTableViewController) *[Contact me](mailto:1933549736@qq.com) to have your app listed here.* ## Related Projects [SSMessagingViewController][ss] [AcaniChat](https://github.com/acani/AcaniChat) ## [MIT License](http://opensource.org/licenses/MIT) You are free to use this as you please. No attribution necessary. **If you use this, please tell me about it!** Copyright © 2013 Robin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [ss]:https://github.com/soffes/ssmessagesviewcontroller [ref1]:http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html#//apple_ref/occ/intf/UITableViewDataSource [ref2]:http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html [img1]:https://github.com/EvoRobin/ChatMessageTableViewController/blob/master/ScreenShot/IMG_0452.PNG?raw=true [img2]:https://github.com/EvoRobin/ChatMessageTableViewController/blob/master/ScreenShot/IMG_0453.PNG?raw=true [img3]:https://github.com/EvoRobin/ChatMessageTableViewController/blob/master/ScreenShot/IMG_0454.PNG?raw=true [img4]:https://github.com/EvoRobin/ChatMessageTableViewController/blob/master/ScreenShot/IMG_0455.PNG?raw=true [link1]:https://github.com/EvoRobin/ChatMessageTableViewController/tree/master/ScreenShot