Compare commits
13 Commits
master
...
iOS6_suppo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
309329cc0c | ||
|
|
92d13ae31e | ||
|
|
351118b20e | ||
|
|
08bcc96f08 | ||
|
|
420c643f86 | ||
|
|
03ac5832c9 | ||
|
|
5d5d6a410b | ||
|
|
68ec26479f | ||
|
|
bf4a2a7525 | ||
|
|
666eace95b | ||
|
|
3a7e8b13dc | ||
|
|
eb485f2184 | ||
|
|
b81cfae779 |
@ -1,7 +0,0 @@
|
||||
highlight-color: "#43D15B"
|
||||
highlight-dark-color: "#319942"
|
||||
darker-color: "#8A998C"
|
||||
darker-dark-color: "#454D46"
|
||||
background-color: "#D7E5CF"
|
||||
alt-link-color: "#00A81C"
|
||||
warning-color: "#0FB82B"
|
||||
@ -4,7 +4,7 @@ before_install:
|
||||
- export LANG=en_US.UTF-8
|
||||
- gem install cocoapods
|
||||
- brew update
|
||||
- if brew outdated | grep -qx xctool; then brew upgrade xctool; fi
|
||||
- brew upgrade xctool
|
||||
|
||||
script:
|
||||
- xctool clean build test -workspace JSQMessages.xcworkspace -scheme JSQMessages -sdk iphonesimulator7.1 ONLY_ACTIVE_ARCH=NO
|
||||
- xctool clean build test -workspace JSMessagesDemo.xcworkspace -scheme JSMessagesDemo -sdk iphonesimulator6.0 ONLY_ACTIVE_ARCH=NO
|
||||
|
||||
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 10 KiB |
@ -1,3 +0,0 @@
|
||||
## How To Contribute
|
||||
|
||||
Please follow these sweet [contribution guidelines](https://github.com/jessesquires/HowToContribute).
|
||||
987
JSMessagesDemo.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,987 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
6404600EC231419EB620AF48 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AFF9601294443728D9481E0 /* libPods.a */; };
|
||||
885BAC4E185421C8004DCD34 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 885BAC4D185421C8004DCD34 /* QuartzCore.framework */; };
|
||||
885BAC4F185421D4004DCD34 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 885BAC4D185421C8004DCD34 /* QuartzCore.framework */; };
|
||||
88A3FB4A182E079E00705327 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88A3FB49182E079E00705327 /* Foundation.framework */; };
|
||||
88A3FB4C182E079E00705327 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88A3FB4B182E079E00705327 /* CoreGraphics.framework */; };
|
||||
88A3FB4E182E079E00705327 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88A3FB4D182E079E00705327 /* UIKit.framework */; };
|
||||
88A3FB54182E079E00705327 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FB52182E079E00705327 /* InfoPlist.strings */; };
|
||||
88A3FB56182E079E00705327 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FB55182E079E00705327 /* main.m */; };
|
||||
88A3FB5A182E079E00705327 /* JSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FB59182E079E00705327 /* JSAppDelegate.m */; };
|
||||
88A3FB63182E079E00705327 /* JSDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FB62182E079E00705327 /* JSDemoViewController.m */; };
|
||||
88A3FB65182E079E00705327 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FB64182E079E00705327 /* Images.xcassets */; };
|
||||
88A3FB6C182E079E00705327 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88A3FB6B182E079E00705327 /* XCTest.framework */; };
|
||||
88A3FB6D182E079E00705327 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88A3FB49182E079E00705327 /* Foundation.framework */; };
|
||||
88A3FB6E182E079E00705327 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88A3FB4D182E079E00705327 /* UIKit.framework */; };
|
||||
88A3FB76182E079E00705327 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FB74182E079E00705327 /* InfoPlist.strings */; };
|
||||
88A3FB78182E079E00705327 /* JSMessagesDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FB77182E079E00705327 /* JSMessagesDemoTests.m */; };
|
||||
88A3FC59182E0EA000705327 /* NSString+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC1D182E0EA000705327 /* NSString+JSMessagesView.m */; };
|
||||
88A3FC5A182E0EA000705327 /* NSString+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC1D182E0EA000705327 /* NSString+JSMessagesView.m */; };
|
||||
88A3FC5D182E0EA000705327 /* UIColor+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC21182E0EA000705327 /* UIColor+JSMessagesView.m */; };
|
||||
88A3FC5E182E0EA000705327 /* UIColor+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC21182E0EA000705327 /* UIColor+JSMessagesView.m */; };
|
||||
88A3FC61182E0EA000705327 /* UIImage+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC25182E0EA000705327 /* UIImage+JSMessagesView.m */; };
|
||||
88A3FC62182E0EA000705327 /* UIImage+JSMessagesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC25182E0EA000705327 /* UIImage+JSMessagesView.m */; };
|
||||
88A3FC63182E0EA000705327 /* JSAvatarImageFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC28182E0EA000705327 /* JSAvatarImageFactory.m */; };
|
||||
88A3FC64182E0EA000705327 /* JSAvatarImageFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC28182E0EA000705327 /* JSAvatarImageFactory.m */; };
|
||||
88A3FC65182E0EA000705327 /* JSBubbleImageViewFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC2A182E0EA000705327 /* JSBubbleImageViewFactory.m */; };
|
||||
88A3FC66182E0EA000705327 /* JSBubbleImageViewFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC2A182E0EA000705327 /* JSBubbleImageViewFactory.m */; };
|
||||
88A3FC67182E0EA000705327 /* JSBubbleMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC2C182E0EA000705327 /* JSBubbleMessageCell.m */; };
|
||||
88A3FC68182E0EA000705327 /* JSBubbleMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC2C182E0EA000705327 /* JSBubbleMessageCell.m */; };
|
||||
88A3FC69182E0EA000705327 /* JSBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC2E182E0EA000705327 /* JSBubbleView.m */; };
|
||||
88A3FC6A182E0EA000705327 /* JSBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC2E182E0EA000705327 /* JSBubbleView.m */; };
|
||||
88A3FC6B182E0EA000705327 /* JSDismissiveTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC30182E0EA000705327 /* JSDismissiveTextView.m */; };
|
||||
88A3FC6C182E0EA000705327 /* JSDismissiveTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC30182E0EA000705327 /* JSDismissiveTextView.m */; };
|
||||
88A3FC6D182E0EA000705327 /* JSMessageInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC32182E0EA000705327 /* JSMessageInputView.m */; };
|
||||
88A3FC6E182E0EA000705327 /* JSMessageInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC32182E0EA000705327 /* JSMessageInputView.m */; };
|
||||
88A3FC6F182E0EA000705327 /* JSMessageSoundEffect.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC34182E0EA000705327 /* JSMessageSoundEffect.m */; };
|
||||
88A3FC70182E0EA000705327 /* JSMessageSoundEffect.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC34182E0EA000705327 /* JSMessageSoundEffect.m */; };
|
||||
88A3FC71182E0EA000705327 /* JSMessagesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC36182E0EA000705327 /* JSMessagesViewController.m */; };
|
||||
88A3FC72182E0EA000705327 /* JSMessagesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC36182E0EA000705327 /* JSMessagesViewController.m */; };
|
||||
88A3FC73182E0EA000705327 /* JSMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC38182E0EA000705327 /* JSMessageTextView.m */; };
|
||||
88A3FC74182E0EA000705327 /* JSMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A3FC38182E0EA000705327 /* JSMessageTextView.m */; };
|
||||
88A3FC75182E0EA000705327 /* bubble-classic-blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC3C182E0EA000705327 /* bubble-classic-blue.png */; };
|
||||
88A3FC76182E0EA000705327 /* bubble-classic-blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC3C182E0EA000705327 /* bubble-classic-blue.png */; };
|
||||
88A3FC77182E0EA000705327 /* bubble-classic-blue@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC3D182E0EA000705327 /* bubble-classic-blue@2x.png */; };
|
||||
88A3FC78182E0EA000705327 /* bubble-classic-blue@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC3D182E0EA000705327 /* bubble-classic-blue@2x.png */; };
|
||||
88A3FC79182E0EA000705327 /* bubble-classic-gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC3E182E0EA000705327 /* bubble-classic-gray.png */; };
|
||||
88A3FC7A182E0EA000705327 /* bubble-classic-gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC3E182E0EA000705327 /* bubble-classic-gray.png */; };
|
||||
88A3FC7B182E0EA000705327 /* bubble-classic-gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC3F182E0EA000705327 /* bubble-classic-gray@2x.png */; };
|
||||
88A3FC7C182E0EA000705327 /* bubble-classic-gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC3F182E0EA000705327 /* bubble-classic-gray@2x.png */; };
|
||||
88A3FC7D182E0EA000705327 /* bubble-classic-green.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC40182E0EA000705327 /* bubble-classic-green.png */; };
|
||||
88A3FC7E182E0EA000705327 /* bubble-classic-green.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC40182E0EA000705327 /* bubble-classic-green.png */; };
|
||||
88A3FC7F182E0EA000705327 /* bubble-classic-green@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC41182E0EA000705327 /* bubble-classic-green@2x.png */; };
|
||||
88A3FC80182E0EA000705327 /* bubble-classic-green@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC41182E0EA000705327 /* bubble-classic-green@2x.png */; };
|
||||
88A3FC89182E0EA000705327 /* bubble-classic-square-blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC47182E0EA000705327 /* bubble-classic-square-blue.png */; };
|
||||
88A3FC8A182E0EA000705327 /* bubble-classic-square-blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC47182E0EA000705327 /* bubble-classic-square-blue.png */; };
|
||||
88A3FC8B182E0EA000705327 /* bubble-classic-square-blue@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC48182E0EA000705327 /* bubble-classic-square-blue@2x.png */; };
|
||||
88A3FC8C182E0EA000705327 /* bubble-classic-square-blue@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC48182E0EA000705327 /* bubble-classic-square-blue@2x.png */; };
|
||||
88A3FC8D182E0EA000705327 /* bubble-classic-square-gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC49182E0EA000705327 /* bubble-classic-square-gray.png */; };
|
||||
88A3FC8E182E0EA000705327 /* bubble-classic-square-gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC49182E0EA000705327 /* bubble-classic-square-gray.png */; };
|
||||
88A3FC8F182E0EA000705327 /* bubble-classic-square-gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC4A182E0EA000705327 /* bubble-classic-square-gray@2x.png */; };
|
||||
88A3FC90182E0EA000705327 /* bubble-classic-square-gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC4A182E0EA000705327 /* bubble-classic-square-gray@2x.png */; };
|
||||
88A3FC91182E0EA000705327 /* bubble-classic-square-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC4B182E0EA000705327 /* bubble-classic-square-selected.png */; };
|
||||
88A3FC92182E0EA000705327 /* bubble-classic-square-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC4B182E0EA000705327 /* bubble-classic-square-selected.png */; };
|
||||
88A3FC93182E0EA000705327 /* bubble-classic-square-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC4C182E0EA000705327 /* bubble-classic-square-selected@2x.png */; };
|
||||
88A3FC94182E0EA000705327 /* bubble-classic-square-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC4C182E0EA000705327 /* bubble-classic-square-selected@2x.png */; };
|
||||
88A3FCA5182E0EA000705327 /* message-received.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC57182E0EA000705327 /* message-received.aiff */; };
|
||||
88A3FCA6182E0EA000705327 /* message-received.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC57182E0EA000705327 /* message-received.aiff */; };
|
||||
88A3FCA7182E0EA000705327 /* message-sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC58182E0EA000705327 /* message-sent.aiff */; };
|
||||
88A3FCA8182E0EA000705327 /* message-sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 88A3FC58182E0EA000705327 /* message-sent.aiff */; };
|
||||
88A6ACFF1839FE87000F56DA /* avatar-placeholder.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6ACFD1839FE87000F56DA /* avatar-placeholder.png */; };
|
||||
88A6AD001839FE87000F56DA /* avatar-placeholder@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6ACFE1839FE87000F56DA /* avatar-placeholder@2x.png */; };
|
||||
88A6AD0E1839FE95000F56DA /* bubble-min-tailless.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD021839FE95000F56DA /* bubble-min-tailless.png */; };
|
||||
88A6AD0F1839FE95000F56DA /* bubble-min-tailless@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD031839FE95000F56DA /* bubble-min-tailless@2x.png */; };
|
||||
88A6AD101839FE95000F56DA /* bubble-min.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD041839FE95000F56DA /* bubble-min.png */; };
|
||||
88A6AD111839FE95000F56DA /* bubble-min@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD051839FE95000F56DA /* bubble-min@2x.png */; };
|
||||
88A6AD121839FE95000F56DA /* bubble-stroked-tailless.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD061839FE95000F56DA /* bubble-stroked-tailless.png */; };
|
||||
88A6AD131839FE95000F56DA /* bubble-stroked-tailless@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD071839FE95000F56DA /* bubble-stroked-tailless@2x.png */; };
|
||||
88A6AD141839FE95000F56DA /* bubble-stroked.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD081839FE95000F56DA /* bubble-stroked.png */; };
|
||||
88A6AD151839FE95000F56DA /* bubble-stroked@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD091839FE95000F56DA /* bubble-stroked@2x.png */; };
|
||||
88A6AD161839FE95000F56DA /* bubble-tailless.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD0A1839FE95000F56DA /* bubble-tailless.png */; };
|
||||
88A6AD171839FE95000F56DA /* bubble-tailless@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD0B1839FE95000F56DA /* bubble-tailless@2x.png */; };
|
||||
88A6AD181839FE95000F56DA /* bubble.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD0C1839FE95000F56DA /* bubble.png */; };
|
||||
88A6AD191839FE95000F56DA /* bubble@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD0D1839FE95000F56DA /* bubble@2x.png */; };
|
||||
88A6AD1F1839FFBB000F56DA /* button-photo.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD1B1839FFBB000F56DA /* button-photo.png */; };
|
||||
88A6AD201839FFBB000F56DA /* button-photo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD1C1839FFBB000F56DA /* button-photo@2x.png */; };
|
||||
88A6AD211839FFBB000F56DA /* input-bar-flat.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD1D1839FFBB000F56DA /* input-bar-flat.png */; };
|
||||
88A6AD221839FFBB000F56DA /* input-bar-flat@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88A6AD1E1839FFBB000F56DA /* input-bar-flat@2x.png */; };
|
||||
88BA157A188F8FB900EDD5C0 /* JSMessageTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88BA1579188F8FB900EDD5C0 /* JSMessageTableView.m */; };
|
||||
88BA157D188F91BE00EDD5C0 /* JSMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 88BA157C188F91BE00EDD5C0 /* JSMessage.m */; };
|
||||
88E43C74183011830045E52D /* input-bar-background.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C6C183011830045E52D /* input-bar-background.png */; };
|
||||
88E43C75183011830045E52D /* input-bar-background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C6D183011830045E52D /* input-bar-background@2x.png */; };
|
||||
88E43C76183011830045E52D /* input-field-cover.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C6E183011830045E52D /* input-field-cover.png */; };
|
||||
88E43C77183011830045E52D /* input-field-cover@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C6F183011830045E52D /* input-field-cover@2x.png */; };
|
||||
88E43C78183011830045E52D /* send-button-pressed.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C70183011830045E52D /* send-button-pressed.png */; };
|
||||
88E43C79183011830045E52D /* send-button-pressed@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C71183011830045E52D /* send-button-pressed@2x.png */; };
|
||||
88E43C7A183011830045E52D /* send-button.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C72183011830045E52D /* send-button.png */; };
|
||||
88E43C7B183011830045E52D /* send-button@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C73183011830045E52D /* send-button@2x.png */; };
|
||||
88E43C7E1830138A0045E52D /* bubble-classic-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C7C1830138A0045E52D /* bubble-classic-selected.png */; };
|
||||
88E43C7F1830138A0045E52D /* bubble-classic-selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C7D1830138A0045E52D /* bubble-classic-selected@2x.png */; };
|
||||
88E43C821830147E0045E52D /* bubble-classic-typing.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C801830147E0045E52D /* bubble-classic-typing.png */; };
|
||||
88E43C831830147E0045E52D /* bubble-classic-typing@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88E43C811830147E0045E52D /* bubble-classic-typing@2x.png */; };
|
||||
FF5F1C19A2B14956BCA42EBB /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AFF9601294443728D9481E0 /* libPods.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
88A3FB6F182E079E00705327 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 88A3FB3E182E079D00705327 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 88A3FB45182E079E00705327;
|
||||
remoteInfo = JSMessagesDemo;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
6AFF9601294443728D9481E0 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
885BAC4D185421C8004DCD34 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
||||
88A3FB46182E079E00705327 /* JSMessagesDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JSMessagesDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
88A3FB49182E079E00705327 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
88A3FB4B182E079E00705327 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
|
||||
88A3FB4D182E079E00705327 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||
88A3FB51182E079E00705327 /* JSMessagesDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JSMessagesDemo-Info.plist"; sourceTree = "<group>"; };
|
||||
88A3FB53182E079E00705327 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
88A3FB55182E079E00705327 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
88A3FB57182E079E00705327 /* JSMessagesDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JSMessagesDemo-Prefix.pch"; sourceTree = "<group>"; };
|
||||
88A3FB58182E079E00705327 /* JSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSAppDelegate.h; sourceTree = "<group>"; };
|
||||
88A3FB59182E079E00705327 /* JSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JSAppDelegate.m; sourceTree = "<group>"; };
|
||||
88A3FB61182E079E00705327 /* JSDemoViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSDemoViewController.h; sourceTree = "<group>"; };
|
||||
88A3FB62182E079E00705327 /* JSDemoViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = JSDemoViewController.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FB64182E079E00705327 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
88A3FB6A182E079E00705327 /* JSMessagesDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JSMessagesDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
88A3FB6B182E079E00705327 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
|
||||
88A3FB73182E079E00705327 /* JSMessagesDemoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JSMessagesDemoTests-Info.plist"; sourceTree = "<group>"; };
|
||||
88A3FB75182E079E00705327 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
88A3FB77182E079E00705327 /* JSMessagesDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JSMessagesDemoTests.m; sourceTree = "<group>"; };
|
||||
88A3FC13182E0B8C00705327 /* JSMessagesViewController.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = JSMessagesViewController.podspec; sourceTree = "<group>"; };
|
||||
88A3FC14182E0B8C00705327 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
|
||||
88A3FC15182E0B8C00705327 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; lineEnding = 0; path = README.md; sourceTree = "<group>"; };
|
||||
88A3FC1C182E0EA000705327 /* NSString+JSMessagesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+JSMessagesView.h"; sourceTree = "<group>"; };
|
||||
88A3FC1D182E0EA000705327 /* NSString+JSMessagesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+JSMessagesView.m"; sourceTree = "<group>"; };
|
||||
88A3FC20182E0EA000705327 /* UIColor+JSMessagesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+JSMessagesView.h"; sourceTree = "<group>"; };
|
||||
88A3FC21182E0EA000705327 /* UIColor+JSMessagesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = "UIColor+JSMessagesView.m"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC24182E0EA000705327 /* UIImage+JSMessagesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+JSMessagesView.h"; sourceTree = "<group>"; };
|
||||
88A3FC25182E0EA000705327 /* UIImage+JSMessagesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = "UIImage+JSMessagesView.m"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC27182E0EA000705327 /* JSAvatarImageFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSAvatarImageFactory.h; sourceTree = "<group>"; };
|
||||
88A3FC28182E0EA000705327 /* JSAvatarImageFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSAvatarImageFactory.m; sourceTree = "<group>"; };
|
||||
88A3FC29182E0EA000705327 /* JSBubbleImageViewFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBubbleImageViewFactory.h; sourceTree = "<group>"; };
|
||||
88A3FC2A182E0EA000705327 /* JSBubbleImageViewFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = JSBubbleImageViewFactory.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC2B182E0EA000705327 /* JSBubbleMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBubbleMessageCell.h; sourceTree = "<group>"; };
|
||||
88A3FC2C182E0EA000705327 /* JSBubbleMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = JSBubbleMessageCell.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC2D182E0EA000705327 /* JSBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBubbleView.h; sourceTree = "<group>"; };
|
||||
88A3FC2E182E0EA000705327 /* JSBubbleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = JSBubbleView.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC2F182E0EA000705327 /* JSDismissiveTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSDismissiveTextView.h; sourceTree = "<group>"; };
|
||||
88A3FC30182E0EA000705327 /* JSDismissiveTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = JSDismissiveTextView.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC31182E0EA000705327 /* JSMessageInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessageInputView.h; sourceTree = "<group>"; };
|
||||
88A3FC32182E0EA000705327 /* JSMessageInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = JSMessageInputView.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC33182E0EA000705327 /* JSMessageSoundEffect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessageSoundEffect.h; sourceTree = "<group>"; };
|
||||
88A3FC34182E0EA000705327 /* JSMessageSoundEffect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSMessageSoundEffect.m; sourceTree = "<group>"; };
|
||||
88A3FC35182E0EA000705327 /* JSMessagesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessagesViewController.h; sourceTree = "<group>"; };
|
||||
88A3FC36182E0EA000705327 /* JSMessagesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = JSMessagesViewController.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC37182E0EA000705327 /* JSMessageTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessageTextView.h; sourceTree = "<group>"; };
|
||||
88A3FC38182E0EA000705327 /* JSMessageTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = JSMessageTextView.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
88A3FC3C182E0EA000705327 /* bubble-classic-blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-blue.png"; sourceTree = "<group>"; };
|
||||
88A3FC3D182E0EA000705327 /* bubble-classic-blue@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-blue@2x.png"; sourceTree = "<group>"; };
|
||||
88A3FC3E182E0EA000705327 /* bubble-classic-gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-gray.png"; sourceTree = "<group>"; };
|
||||
88A3FC3F182E0EA000705327 /* bubble-classic-gray@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-gray@2x.png"; sourceTree = "<group>"; };
|
||||
88A3FC40182E0EA000705327 /* bubble-classic-green.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-green.png"; sourceTree = "<group>"; };
|
||||
88A3FC41182E0EA000705327 /* bubble-classic-green@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-green@2x.png"; sourceTree = "<group>"; };
|
||||
88A3FC47182E0EA000705327 /* bubble-classic-square-blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-square-blue.png"; sourceTree = "<group>"; };
|
||||
88A3FC48182E0EA000705327 /* bubble-classic-square-blue@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-square-blue@2x.png"; sourceTree = "<group>"; };
|
||||
88A3FC49182E0EA000705327 /* bubble-classic-square-gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-square-gray.png"; sourceTree = "<group>"; };
|
||||
88A3FC4A182E0EA000705327 /* bubble-classic-square-gray@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-square-gray@2x.png"; sourceTree = "<group>"; };
|
||||
88A3FC4B182E0EA000705327 /* bubble-classic-square-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-square-selected.png"; sourceTree = "<group>"; };
|
||||
88A3FC4C182E0EA000705327 /* bubble-classic-square-selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-square-selected@2x.png"; sourceTree = "<group>"; };
|
||||
88A3FC57182E0EA000705327 /* message-received.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = "message-received.aiff"; sourceTree = "<group>"; };
|
||||
88A3FC58182E0EA000705327 /* message-sent.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = "message-sent.aiff"; sourceTree = "<group>"; };
|
||||
88A6ACFD1839FE87000F56DA /* avatar-placeholder.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-placeholder.png"; sourceTree = "<group>"; };
|
||||
88A6ACFE1839FE87000F56DA /* avatar-placeholder@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-placeholder@2x.png"; sourceTree = "<group>"; };
|
||||
88A6AD021839FE95000F56DA /* bubble-min-tailless.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-min-tailless.png"; sourceTree = "<group>"; };
|
||||
88A6AD031839FE95000F56DA /* bubble-min-tailless@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-min-tailless@2x.png"; sourceTree = "<group>"; };
|
||||
88A6AD041839FE95000F56DA /* bubble-min.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-min.png"; sourceTree = "<group>"; };
|
||||
88A6AD051839FE95000F56DA /* bubble-min@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-min@2x.png"; sourceTree = "<group>"; };
|
||||
88A6AD061839FE95000F56DA /* bubble-stroked-tailless.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-stroked-tailless.png"; sourceTree = "<group>"; };
|
||||
88A6AD071839FE95000F56DA /* bubble-stroked-tailless@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-stroked-tailless@2x.png"; sourceTree = "<group>"; };
|
||||
88A6AD081839FE95000F56DA /* bubble-stroked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-stroked.png"; sourceTree = "<group>"; };
|
||||
88A6AD091839FE95000F56DA /* bubble-stroked@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-stroked@2x.png"; sourceTree = "<group>"; };
|
||||
88A6AD0A1839FE95000F56DA /* bubble-tailless.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-tailless.png"; sourceTree = "<group>"; };
|
||||
88A6AD0B1839FE95000F56DA /* bubble-tailless@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-tailless@2x.png"; sourceTree = "<group>"; };
|
||||
88A6AD0C1839FE95000F56DA /* bubble.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bubble.png; sourceTree = "<group>"; };
|
||||
88A6AD0D1839FE95000F56DA /* bubble@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble@2x.png"; sourceTree = "<group>"; };
|
||||
88A6AD1B1839FFBB000F56DA /* button-photo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "button-photo.png"; sourceTree = "<group>"; };
|
||||
88A6AD1C1839FFBB000F56DA /* button-photo@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "button-photo@2x.png"; sourceTree = "<group>"; };
|
||||
88A6AD1D1839FFBB000F56DA /* input-bar-flat.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-bar-flat.png"; sourceTree = "<group>"; };
|
||||
88A6AD1E1839FFBB000F56DA /* input-bar-flat@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-bar-flat@2x.png"; sourceTree = "<group>"; };
|
||||
88BA1578188F8FB900EDD5C0 /* JSMessageTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessageTableView.h; sourceTree = "<group>"; };
|
||||
88BA1579188F8FB900EDD5C0 /* JSMessageTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSMessageTableView.m; sourceTree = "<group>"; };
|
||||
88BA157B188F91BE00EDD5C0 /* JSMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessage.h; sourceTree = "<group>"; };
|
||||
88BA157C188F91BE00EDD5C0 /* JSMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSMessage.m; sourceTree = "<group>"; };
|
||||
88DB7C74189223F100D7117F /* JSMessageData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMessageData.h; sourceTree = "<group>"; };
|
||||
88E43C6C183011830045E52D /* input-bar-background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-bar-background.png"; sourceTree = "<group>"; };
|
||||
88E43C6D183011830045E52D /* input-bar-background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-bar-background@2x.png"; sourceTree = "<group>"; };
|
||||
88E43C6E183011830045E52D /* input-field-cover.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-field-cover.png"; sourceTree = "<group>"; };
|
||||
88E43C6F183011830045E52D /* input-field-cover@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "input-field-cover@2x.png"; sourceTree = "<group>"; };
|
||||
88E43C70183011830045E52D /* send-button-pressed.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "send-button-pressed.png"; sourceTree = "<group>"; };
|
||||
88E43C71183011830045E52D /* send-button-pressed@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "send-button-pressed@2x.png"; sourceTree = "<group>"; };
|
||||
88E43C72183011830045E52D /* send-button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "send-button.png"; sourceTree = "<group>"; };
|
||||
88E43C73183011830045E52D /* send-button@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "send-button@2x.png"; sourceTree = "<group>"; };
|
||||
88E43C7C1830138A0045E52D /* bubble-classic-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-selected.png"; sourceTree = "<group>"; };
|
||||
88E43C7D1830138A0045E52D /* bubble-classic-selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-selected@2x.png"; sourceTree = "<group>"; };
|
||||
88E43C801830147E0045E52D /* bubble-classic-typing.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-typing.png"; sourceTree = "<group>"; };
|
||||
88E43C811830147E0045E52D /* bubble-classic-typing@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bubble-classic-typing@2x.png"; sourceTree = "<group>"; };
|
||||
A8C230F1717F4008B2BFAEA6 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
88A3FB43182E079E00705327 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
885BAC4F185421D4004DCD34 /* QuartzCore.framework in Frameworks */,
|
||||
88A3FB4C182E079E00705327 /* CoreGraphics.framework in Frameworks */,
|
||||
88A3FB4E182E079E00705327 /* UIKit.framework in Frameworks */,
|
||||
88A3FB4A182E079E00705327 /* Foundation.framework in Frameworks */,
|
||||
6404600EC231419EB620AF48 /* libPods.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
88A3FB67182E079E00705327 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
885BAC4E185421C8004DCD34 /* QuartzCore.framework in Frameworks */,
|
||||
88A3FB6C182E079E00705327 /* XCTest.framework in Frameworks */,
|
||||
88A3FB6E182E079E00705327 /* UIKit.framework in Frameworks */,
|
||||
88A3FB6D182E079E00705327 /* Foundation.framework in Frameworks */,
|
||||
FF5F1C19A2B14956BCA42EBB /* libPods.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
88A3FB3D182E079D00705327 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC19182E0EA000705327 /* JSMessagesViewController */,
|
||||
88A3FC13182E0B8C00705327 /* JSMessagesViewController.podspec */,
|
||||
88A3FC14182E0B8C00705327 /* LICENSE.txt */,
|
||||
88A3FC15182E0B8C00705327 /* README.md */,
|
||||
88A3FB4F182E079E00705327 /* JSMessagesDemo */,
|
||||
88A3FB71182E079E00705327 /* JSMessagesDemoTests */,
|
||||
88A3FB48182E079E00705327 /* Frameworks */,
|
||||
88A3FB47182E079E00705327 /* Products */,
|
||||
A8C230F1717F4008B2BFAEA6 /* Pods.xcconfig */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FB47182E079E00705327 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FB46182E079E00705327 /* JSMessagesDemo.app */,
|
||||
88A3FB6A182E079E00705327 /* JSMessagesDemoTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FB48182E079E00705327 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FB4B182E079E00705327 /* CoreGraphics.framework */,
|
||||
88A3FB49182E079E00705327 /* Foundation.framework */,
|
||||
885BAC4D185421C8004DCD34 /* QuartzCore.framework */,
|
||||
88A3FB4D182E079E00705327 /* UIKit.framework */,
|
||||
88A3FB6B182E079E00705327 /* XCTest.framework */,
|
||||
6AFF9601294443728D9481E0 /* libPods.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FB4F182E079E00705327 /* JSMessagesDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FB58182E079E00705327 /* JSAppDelegate.h */,
|
||||
88A3FB59182E079E00705327 /* JSAppDelegate.m */,
|
||||
88A3FB61182E079E00705327 /* JSDemoViewController.h */,
|
||||
88A3FB62182E079E00705327 /* JSDemoViewController.m */,
|
||||
88A3FB64182E079E00705327 /* Images.xcassets */,
|
||||
88A3FB50182E079E00705327 /* Supporting Files */,
|
||||
);
|
||||
path = JSMessagesDemo;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FB50182E079E00705327 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FB51182E079E00705327 /* JSMessagesDemo-Info.plist */,
|
||||
88A3FB52182E079E00705327 /* InfoPlist.strings */,
|
||||
88A3FB55182E079E00705327 /* main.m */,
|
||||
88A3FB57182E079E00705327 /* JSMessagesDemo-Prefix.pch */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FB71182E079E00705327 /* JSMessagesDemoTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FB77182E079E00705327 /* JSMessagesDemoTests.m */,
|
||||
88A3FB72182E079E00705327 /* Supporting Files */,
|
||||
);
|
||||
path = JSMessagesDemoTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FB72182E079E00705327 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FB73182E079E00705327 /* JSMessagesDemoTests-Info.plist */,
|
||||
88A3FB74182E079E00705327 /* InfoPlist.strings */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC19182E0EA000705327 /* JSMessagesViewController */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC1A182E0EA000705327 /* Classes */,
|
||||
88A3FC39182E0EA000705327 /* Resources */,
|
||||
);
|
||||
path = JSMessagesViewController;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC1A182E0EA000705327 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC1B182E0EA000705327 /* Categories */,
|
||||
88A3FC26182E0EA000705327 /* Factories */,
|
||||
88A3FC2B182E0EA000705327 /* JSBubbleMessageCell.h */,
|
||||
88A3FC2C182E0EA000705327 /* JSBubbleMessageCell.m */,
|
||||
88A3FC2D182E0EA000705327 /* JSBubbleView.h */,
|
||||
88A3FC2E182E0EA000705327 /* JSBubbleView.m */,
|
||||
88A3FC2F182E0EA000705327 /* JSDismissiveTextView.h */,
|
||||
88A3FC30182E0EA000705327 /* JSDismissiveTextView.m */,
|
||||
88BA157B188F91BE00EDD5C0 /* JSMessage.h */,
|
||||
88BA157C188F91BE00EDD5C0 /* JSMessage.m */,
|
||||
88DB7C74189223F100D7117F /* JSMessageData.h */,
|
||||
88A3FC31182E0EA000705327 /* JSMessageInputView.h */,
|
||||
88A3FC32182E0EA000705327 /* JSMessageInputView.m */,
|
||||
88A3FC33182E0EA000705327 /* JSMessageSoundEffect.h */,
|
||||
88A3FC34182E0EA000705327 /* JSMessageSoundEffect.m */,
|
||||
88A3FC35182E0EA000705327 /* JSMessagesViewController.h */,
|
||||
88A3FC36182E0EA000705327 /* JSMessagesViewController.m */,
|
||||
88BA1578188F8FB900EDD5C0 /* JSMessageTableView.h */,
|
||||
88BA1579188F8FB900EDD5C0 /* JSMessageTableView.m */,
|
||||
88A3FC37182E0EA000705327 /* JSMessageTextView.h */,
|
||||
88A3FC38182E0EA000705327 /* JSMessageTextView.m */,
|
||||
);
|
||||
path = Classes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC1B182E0EA000705327 /* Categories */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC1C182E0EA000705327 /* NSString+JSMessagesView.h */,
|
||||
88A3FC1D182E0EA000705327 /* NSString+JSMessagesView.m */,
|
||||
88A3FC20182E0EA000705327 /* UIColor+JSMessagesView.h */,
|
||||
88A3FC21182E0EA000705327 /* UIColor+JSMessagesView.m */,
|
||||
88A3FC24182E0EA000705327 /* UIImage+JSMessagesView.h */,
|
||||
88A3FC25182E0EA000705327 /* UIImage+JSMessagesView.m */,
|
||||
);
|
||||
path = Categories;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC26182E0EA000705327 /* Factories */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC27182E0EA000705327 /* JSAvatarImageFactory.h */,
|
||||
88A3FC28182E0EA000705327 /* JSAvatarImageFactory.m */,
|
||||
88A3FC29182E0EA000705327 /* JSBubbleImageViewFactory.h */,
|
||||
88A3FC2A182E0EA000705327 /* JSBubbleImageViewFactory.m */,
|
||||
);
|
||||
path = Factories;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC39182E0EA000705327 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC3A182E0EA000705327 /* Images */,
|
||||
88A3FC56182E0EA000705327 /* Sounds */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC3A182E0EA000705327 /* Images */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A6ACFC1839FE87000F56DA /* avatar */,
|
||||
88A3FC3B182E0EA000705327 /* bubble-classic */,
|
||||
88A3FC46182E0EA000705327 /* bubble-classic-square */,
|
||||
88A6AD011839FE95000F56DA /* bubble-flat */,
|
||||
88A3FC4D182E0EA000705327 /* input-bar-classic */,
|
||||
88A6AD1A1839FFBB000F56DA /* input-bar-flat */,
|
||||
);
|
||||
path = Images;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC3B182E0EA000705327 /* bubble-classic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC3C182E0EA000705327 /* bubble-classic-blue.png */,
|
||||
88A3FC3D182E0EA000705327 /* bubble-classic-blue@2x.png */,
|
||||
88A3FC3E182E0EA000705327 /* bubble-classic-gray.png */,
|
||||
88A3FC3F182E0EA000705327 /* bubble-classic-gray@2x.png */,
|
||||
88A3FC40182E0EA000705327 /* bubble-classic-green.png */,
|
||||
88A3FC41182E0EA000705327 /* bubble-classic-green@2x.png */,
|
||||
88E43C7C1830138A0045E52D /* bubble-classic-selected.png */,
|
||||
88E43C7D1830138A0045E52D /* bubble-classic-selected@2x.png */,
|
||||
88E43C801830147E0045E52D /* bubble-classic-typing.png */,
|
||||
88E43C811830147E0045E52D /* bubble-classic-typing@2x.png */,
|
||||
);
|
||||
path = "bubble-classic";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC46182E0EA000705327 /* bubble-classic-square */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC47182E0EA000705327 /* bubble-classic-square-blue.png */,
|
||||
88A3FC48182E0EA000705327 /* bubble-classic-square-blue@2x.png */,
|
||||
88A3FC49182E0EA000705327 /* bubble-classic-square-gray.png */,
|
||||
88A3FC4A182E0EA000705327 /* bubble-classic-square-gray@2x.png */,
|
||||
88A3FC4B182E0EA000705327 /* bubble-classic-square-selected.png */,
|
||||
88A3FC4C182E0EA000705327 /* bubble-classic-square-selected@2x.png */,
|
||||
);
|
||||
path = "bubble-classic-square";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC4D182E0EA000705327 /* input-bar-classic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88E43C6C183011830045E52D /* input-bar-background.png */,
|
||||
88E43C6D183011830045E52D /* input-bar-background@2x.png */,
|
||||
88E43C6E183011830045E52D /* input-field-cover.png */,
|
||||
88E43C6F183011830045E52D /* input-field-cover@2x.png */,
|
||||
88E43C70183011830045E52D /* send-button-pressed.png */,
|
||||
88E43C71183011830045E52D /* send-button-pressed@2x.png */,
|
||||
88E43C72183011830045E52D /* send-button.png */,
|
||||
88E43C73183011830045E52D /* send-button@2x.png */,
|
||||
);
|
||||
path = "input-bar-classic";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FC56182E0EA000705327 /* Sounds */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A3FC57182E0EA000705327 /* message-received.aiff */,
|
||||
88A3FC58182E0EA000705327 /* message-sent.aiff */,
|
||||
);
|
||||
path = Sounds;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A6ACFC1839FE87000F56DA /* avatar */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A6ACFD1839FE87000F56DA /* avatar-placeholder.png */,
|
||||
88A6ACFE1839FE87000F56DA /* avatar-placeholder@2x.png */,
|
||||
);
|
||||
path = avatar;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A6AD011839FE95000F56DA /* bubble-flat */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A6AD021839FE95000F56DA /* bubble-min-tailless.png */,
|
||||
88A6AD031839FE95000F56DA /* bubble-min-tailless@2x.png */,
|
||||
88A6AD041839FE95000F56DA /* bubble-min.png */,
|
||||
88A6AD051839FE95000F56DA /* bubble-min@2x.png */,
|
||||
88A6AD061839FE95000F56DA /* bubble-stroked-tailless.png */,
|
||||
88A6AD071839FE95000F56DA /* bubble-stroked-tailless@2x.png */,
|
||||
88A6AD081839FE95000F56DA /* bubble-stroked.png */,
|
||||
88A6AD091839FE95000F56DA /* bubble-stroked@2x.png */,
|
||||
88A6AD0A1839FE95000F56DA /* bubble-tailless.png */,
|
||||
88A6AD0B1839FE95000F56DA /* bubble-tailless@2x.png */,
|
||||
88A6AD0C1839FE95000F56DA /* bubble.png */,
|
||||
88A6AD0D1839FE95000F56DA /* bubble@2x.png */,
|
||||
);
|
||||
path = "bubble-flat";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A6AD1A1839FFBB000F56DA /* input-bar-flat */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88A6AD1B1839FFBB000F56DA /* button-photo.png */,
|
||||
88A6AD1C1839FFBB000F56DA /* button-photo@2x.png */,
|
||||
88A6AD1D1839FFBB000F56DA /* input-bar-flat.png */,
|
||||
88A6AD1E1839FFBB000F56DA /* input-bar-flat@2x.png */,
|
||||
);
|
||||
path = "input-bar-flat";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
88A3FB45182E079E00705327 /* JSMessagesDemo */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 88A3FB7B182E079E00705327 /* Build configuration list for PBXNativeTarget "JSMessagesDemo" */;
|
||||
buildPhases = (
|
||||
25C7F7328CD4459AB0413ACE /* Check Pods Manifest.lock */,
|
||||
88A3FB42182E079E00705327 /* Sources */,
|
||||
88A3FB43182E079E00705327 /* Frameworks */,
|
||||
88A3FB44182E079E00705327 /* Resources */,
|
||||
AF4DAD50B0E541D591936B27 /* Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = JSMessagesDemo;
|
||||
productName = JSMessagesDemo;
|
||||
productReference = 88A3FB46182E079E00705327 /* JSMessagesDemo.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
88A3FB69182E079E00705327 /* JSMessagesDemoTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 88A3FB7E182E079E00705327 /* Build configuration list for PBXNativeTarget "JSMessagesDemoTests" */;
|
||||
buildPhases = (
|
||||
B6BEEE0608204470B44ABB11 /* Check Pods Manifest.lock */,
|
||||
88A3FB66182E079E00705327 /* Sources */,
|
||||
88A3FB67182E079E00705327 /* Frameworks */,
|
||||
88A3FB68182E079E00705327 /* Resources */,
|
||||
D763FFBCA0C448878FA4C696 /* Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
88A3FB70182E079E00705327 /* PBXTargetDependency */,
|
||||
);
|
||||
name = JSMessagesDemoTests;
|
||||
productName = JSMessagesDemoTests;
|
||||
productReference = 88A3FB6A182E079E00705327 /* JSMessagesDemoTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
88A3FB3E182E079D00705327 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
CLASSPREFIX = JS;
|
||||
LastUpgradeCheck = 0510;
|
||||
ORGANIZATIONNAME = "Hexed Bits";
|
||||
TargetAttributes = {
|
||||
88A3FB69182E079E00705327 = {
|
||||
TestTargetID = 88A3FB45182E079E00705327;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 88A3FB41182E079D00705327 /* Build configuration list for PBXProject "JSMessagesDemo" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 88A3FB3D182E079D00705327;
|
||||
productRefGroup = 88A3FB47182E079E00705327 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
88A3FB45182E079E00705327 /* JSMessagesDemo */,
|
||||
88A3FB69182E079E00705327 /* JSMessagesDemoTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
88A3FB44182E079E00705327 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
88E43C831830147E0045E52D /* bubble-classic-typing@2x.png in Resources */,
|
||||
88A6AD111839FE95000F56DA /* bubble-min@2x.png in Resources */,
|
||||
88A6AD141839FE95000F56DA /* bubble-stroked.png in Resources */,
|
||||
88A6AD201839FFBB000F56DA /* button-photo@2x.png in Resources */,
|
||||
88A6AD001839FE87000F56DA /* avatar-placeholder@2x.png in Resources */,
|
||||
88A6AD0F1839FE95000F56DA /* bubble-min-tailless@2x.png in Resources */,
|
||||
88A3FC75182E0EA000705327 /* bubble-classic-blue.png in Resources */,
|
||||
88E43C77183011830045E52D /* input-field-cover@2x.png in Resources */,
|
||||
88A6AD101839FE95000F56DA /* bubble-min.png in Resources */,
|
||||
88E43C7A183011830045E52D /* send-button.png in Resources */,
|
||||
88A3FC7F182E0EA000705327 /* bubble-classic-green@2x.png in Resources */,
|
||||
88A6AD1F1839FFBB000F56DA /* button-photo.png in Resources */,
|
||||
88A3FCA5182E0EA000705327 /* message-received.aiff in Resources */,
|
||||
88A6AD211839FFBB000F56DA /* input-bar-flat.png in Resources */,
|
||||
88A3FC8B182E0EA000705327 /* bubble-classic-square-blue@2x.png in Resources */,
|
||||
88A6AD0E1839FE95000F56DA /* bubble-min-tailless.png in Resources */,
|
||||
88A6AD151839FE95000F56DA /* bubble-stroked@2x.png in Resources */,
|
||||
88A3FC8F182E0EA000705327 /* bubble-classic-square-gray@2x.png in Resources */,
|
||||
88A6AD161839FE95000F56DA /* bubble-tailless.png in Resources */,
|
||||
88E43C76183011830045E52D /* input-field-cover.png in Resources */,
|
||||
88A3FC7D182E0EA000705327 /* bubble-classic-green.png in Resources */,
|
||||
88E43C79183011830045E52D /* send-button-pressed@2x.png in Resources */,
|
||||
88E43C78183011830045E52D /* send-button-pressed.png in Resources */,
|
||||
88E43C7B183011830045E52D /* send-button@2x.png in Resources */,
|
||||
88A6AD171839FE95000F56DA /* bubble-tailless@2x.png in Resources */,
|
||||
88A6AD121839FE95000F56DA /* bubble-stroked-tailless.png in Resources */,
|
||||
88A3FC93182E0EA000705327 /* bubble-classic-square-selected@2x.png in Resources */,
|
||||
88A3FC91182E0EA000705327 /* bubble-classic-square-selected.png in Resources */,
|
||||
88A6AD221839FFBB000F56DA /* input-bar-flat@2x.png in Resources */,
|
||||
88E43C7E1830138A0045E52D /* bubble-classic-selected.png in Resources */,
|
||||
88A3FC8D182E0EA000705327 /* bubble-classic-square-gray.png in Resources */,
|
||||
88A3FC89182E0EA000705327 /* bubble-classic-square-blue.png in Resources */,
|
||||
88A3FB65182E079E00705327 /* Images.xcassets in Resources */,
|
||||
88A6AD181839FE95000F56DA /* bubble.png in Resources */,
|
||||
88A6AD191839FE95000F56DA /* bubble@2x.png in Resources */,
|
||||
88E43C821830147E0045E52D /* bubble-classic-typing.png in Resources */,
|
||||
88A3FC79182E0EA000705327 /* bubble-classic-gray.png in Resources */,
|
||||
88A3FB54182E079E00705327 /* InfoPlist.strings in Resources */,
|
||||
88A3FC7B182E0EA000705327 /* bubble-classic-gray@2x.png in Resources */,
|
||||
88E43C74183011830045E52D /* input-bar-background.png in Resources */,
|
||||
88A6ACFF1839FE87000F56DA /* avatar-placeholder.png in Resources */,
|
||||
88E43C7F1830138A0045E52D /* bubble-classic-selected@2x.png in Resources */,
|
||||
88E43C75183011830045E52D /* input-bar-background@2x.png in Resources */,
|
||||
88A3FC77182E0EA000705327 /* bubble-classic-blue@2x.png in Resources */,
|
||||
88A6AD131839FE95000F56DA /* bubble-stroked-tailless@2x.png in Resources */,
|
||||
88A3FCA7182E0EA000705327 /* message-sent.aiff in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
88A3FB68182E079E00705327 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
88A3FC8E182E0EA000705327 /* bubble-classic-square-gray.png in Resources */,
|
||||
88A3FC78182E0EA000705327 /* bubble-classic-blue@2x.png in Resources */,
|
||||
88A3FCA6182E0EA000705327 /* message-received.aiff in Resources */,
|
||||
88A3FC8C182E0EA000705327 /* bubble-classic-square-blue@2x.png in Resources */,
|
||||
88A3FC7A182E0EA000705327 /* bubble-classic-gray.png in Resources */,
|
||||
88A3FC7E182E0EA000705327 /* bubble-classic-green.png in Resources */,
|
||||
88A3FC7C182E0EA000705327 /* bubble-classic-gray@2x.png in Resources */,
|
||||
88A3FCA8182E0EA000705327 /* message-sent.aiff in Resources */,
|
||||
88A3FC76182E0EA000705327 /* bubble-classic-blue.png in Resources */,
|
||||
88A3FC94182E0EA000705327 /* bubble-classic-square-selected@2x.png in Resources */,
|
||||
88A3FC90182E0EA000705327 /* bubble-classic-square-gray@2x.png in Resources */,
|
||||
88A3FB76182E079E00705327 /* InfoPlist.strings in Resources */,
|
||||
88A3FC80182E0EA000705327 /* bubble-classic-green@2x.png in Resources */,
|
||||
88A3FC8A182E0EA000705327 /* bubble-classic-square-blue.png in Resources */,
|
||||
88A3FC92182E0EA000705327 /* bubble-classic-square-selected.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
25C7F7328CD4459AB0413ACE /* Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
AF4DAD50B0E541D591936B27 /* Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B6BEEE0608204470B44ABB11 /* Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
D763FFBCA0C448878FA4C696 /* Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
88A3FB42182E079E00705327 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
88A3FC67182E0EA000705327 /* JSBubbleMessageCell.m in Sources */,
|
||||
88A3FC6B182E0EA000705327 /* JSDismissiveTextView.m in Sources */,
|
||||
88BA157D188F91BE00EDD5C0 /* JSMessage.m in Sources */,
|
||||
88A3FC6F182E0EA000705327 /* JSMessageSoundEffect.m in Sources */,
|
||||
88A3FC69182E0EA000705327 /* JSBubbleView.m in Sources */,
|
||||
88BA157A188F8FB900EDD5C0 /* JSMessageTableView.m in Sources */,
|
||||
88A3FB56182E079E00705327 /* main.m in Sources */,
|
||||
88A3FC5D182E0EA000705327 /* UIColor+JSMessagesView.m in Sources */,
|
||||
88A3FB63182E079E00705327 /* JSDemoViewController.m in Sources */,
|
||||
88A3FC6D182E0EA000705327 /* JSMessageInputView.m in Sources */,
|
||||
88A3FC63182E0EA000705327 /* JSAvatarImageFactory.m in Sources */,
|
||||
88A3FC71182E0EA000705327 /* JSMessagesViewController.m in Sources */,
|
||||
88A3FC73182E0EA000705327 /* JSMessageTextView.m in Sources */,
|
||||
88A3FB5A182E079E00705327 /* JSAppDelegate.m in Sources */,
|
||||
88A3FC65182E0EA000705327 /* JSBubbleImageViewFactory.m in Sources */,
|
||||
88A3FC59182E0EA000705327 /* NSString+JSMessagesView.m in Sources */,
|
||||
88A3FC61182E0EA000705327 /* UIImage+JSMessagesView.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
88A3FB66182E079E00705327 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
88A3FC68182E0EA000705327 /* JSBubbleMessageCell.m in Sources */,
|
||||
88A3FB78182E079E00705327 /* JSMessagesDemoTests.m in Sources */,
|
||||
88A3FC5A182E0EA000705327 /* NSString+JSMessagesView.m in Sources */,
|
||||
88A3FC64182E0EA000705327 /* JSAvatarImageFactory.m in Sources */,
|
||||
88A3FC6A182E0EA000705327 /* JSBubbleView.m in Sources */,
|
||||
88A3FC66182E0EA000705327 /* JSBubbleImageViewFactory.m in Sources */,
|
||||
88A3FC74182E0EA000705327 /* JSMessageTextView.m in Sources */,
|
||||
88A3FC70182E0EA000705327 /* JSMessageSoundEffect.m in Sources */,
|
||||
88A3FC6C182E0EA000705327 /* JSDismissiveTextView.m in Sources */,
|
||||
88A3FC72182E0EA000705327 /* JSMessagesViewController.m in Sources */,
|
||||
88A3FC6E182E0EA000705327 /* JSMessageInputView.m in Sources */,
|
||||
88A3FC62182E0EA000705327 /* UIImage+JSMessagesView.m in Sources */,
|
||||
88A3FC5E182E0EA000705327 /* UIColor+JSMessagesView.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
88A3FB70182E079E00705327 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 88A3FB45182E079E00705327 /* JSMessagesDemo */;
|
||||
targetProxy = 88A3FB6F182E079E00705327 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
88A3FB52182E079E00705327 /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
88A3FB53182E079E00705327 /* en */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88A3FB74182E079E00705327 /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
88A3FB75182E079E00705327 /* en */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
88A3FB79182E079E00705327 /* 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;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
88A3FB7A182E079E00705327 /* 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;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
88A3FB7C182E079E00705327 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A8C230F1717F4008B2BFAEA6 /* Pods.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "JSMessagesDemo/JSMessagesDemo-Prefix.pch";
|
||||
INFOPLIST_FILE = "JSMessagesDemo/JSMessagesDemo-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
88A3FB7D182E079E00705327 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A8C230F1717F4008B2BFAEA6 /* Pods.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "JSMessagesDemo/JSMessagesDemo-Prefix.pch";
|
||||
INFOPLIST_FILE = "JSMessagesDemo/JSMessagesDemo-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
88A3FB7F182E079E00705327 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A8C230F1717F4008B2BFAEA6 /* Pods.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/JSMessagesDemo.app/JSMessagesDemo";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
"$(DEVELOPER_FRAMEWORKS_DIR)",
|
||||
);
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "JSMessagesDemo/JSMessagesDemo-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"\"${PODS_ROOT}/Headers\"",
|
||||
"\"${PODS_ROOT}/Headers/JSQSystemSoundPlayer\"",
|
||||
);
|
||||
INFOPLIST_FILE = "JSMessagesDemoTests/JSMessagesDemoTests-Info.plist";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
88A3FB80182E079E00705327 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A8C230F1717F4008B2BFAEA6 /* Pods.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/JSMessagesDemo.app/JSMessagesDemo";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
"$(DEVELOPER_FRAMEWORKS_DIR)",
|
||||
);
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "JSMessagesDemo/JSMessagesDemo-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"\"${PODS_ROOT}/Headers\"",
|
||||
"\"${PODS_ROOT}/Headers/JSQSystemSoundPlayer\"",
|
||||
);
|
||||
INFOPLIST_FILE = "JSMessagesDemoTests/JSMessagesDemoTests-Info.plist";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
88A3FB41182E079D00705327 /* Build configuration list for PBXProject "JSMessagesDemo" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
88A3FB79182E079E00705327 /* Debug */,
|
||||
88A3FB7A182E079E00705327 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
88A3FB7B182E079E00705327 /* Build configuration list for PBXNativeTarget "JSMessagesDemo" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
88A3FB7C182E079E00705327 /* Debug */,
|
||||
88A3FB7D182E079E00705327 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
88A3FB7E182E079E00705327 /* Build configuration list for PBXNativeTarget "JSMessagesDemoTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
88A3FB7F182E079E00705327 /* Debug */,
|
||||
88A3FB80182E079E00705327 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 88A3FB3E182E079D00705327 /* Project object */;
|
||||
}
|
||||
@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:JSQMessages.xcodeproj">
|
||||
location = "self:JSMessagesDemo.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0600"
|
||||
LastUpgradeVersion = "0510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@ -14,7 +14,7 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4F6B28C25B7B1259BD8C5B8B"
|
||||
BlueprintIdentifier = "5B185E1CBBDD4078BDD6E5B6"
|
||||
BuildableName = "libPods.a"
|
||||
BlueprintName = "Pods"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
@ -28,24 +28,24 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "88A25F0119D8DEC400924534"
|
||||
BuildableName = "JSQMessages.app"
|
||||
BlueprintName = "JSQMessages"
|
||||
ReferencedContainer = "container:JSQMessages.xcodeproj">
|
||||
BlueprintIdentifier = "88A3FB45182E079E00705327"
|
||||
BuildableName = "JSMessagesDemo.app"
|
||||
BlueprintName = "JSMessagesDemo"
|
||||
ReferencedContainer = "container:JSMessagesDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "88A25F1A19D8DEC400924534"
|
||||
BuildableName = "JSQMessagesTests.xctest"
|
||||
BlueprintName = "JSQMessagesTests"
|
||||
ReferencedContainer = "container:JSQMessages.xcodeproj">
|
||||
BlueprintIdentifier = "88A3FB69182E079E00705327"
|
||||
BuildableName = "JSMessagesDemoTests.xctest"
|
||||
BlueprintName = "JSMessagesDemoTests"
|
||||
ReferencedContainer = "container:JSMessagesDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
@ -60,20 +60,20 @@
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "88A25F1A19D8DEC400924534"
|
||||
BuildableName = "JSQMessagesTests.xctest"
|
||||
BlueprintName = "JSQMessagesTests"
|
||||
ReferencedContainer = "container:JSQMessages.xcodeproj">
|
||||
BlueprintIdentifier = "88A3FB69182E079E00705327"
|
||||
BuildableName = "JSMessagesDemoTests.xctest"
|
||||
BlueprintName = "JSMessagesDemoTests"
|
||||
ReferencedContainer = "container:JSMessagesDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "88A25F0119D8DEC400924534"
|
||||
BuildableName = "JSQMessages.app"
|
||||
BlueprintName = "JSQMessages"
|
||||
ReferencedContainer = "container:JSQMessages.xcodeproj">
|
||||
BlueprintIdentifier = "88A3FB45182E079E00705327"
|
||||
BuildableName = "JSMessagesDemo.app"
|
||||
BlueprintName = "JSMessagesDemo"
|
||||
ReferencedContainer = "container:JSMessagesDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
@ -89,10 +89,10 @@
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "88A25F0119D8DEC400924534"
|
||||
BuildableName = "JSQMessages.app"
|
||||
BlueprintName = "JSQMessages"
|
||||
ReferencedContainer = "container:JSQMessages.xcodeproj">
|
||||
BlueprintIdentifier = "88A3FB45182E079E00705327"
|
||||
BuildableName = "JSMessagesDemo.app"
|
||||
BlueprintName = "JSMessagesDemo"
|
||||
ReferencedContainer = "container:JSMessagesDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
@ -107,10 +107,10 @@
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "88A25F0119D8DEC400924534"
|
||||
BuildableName = "JSQMessages.app"
|
||||
BlueprintName = "JSQMessages"
|
||||
ReferencedContainer = "container:JSQMessages.xcodeproj">
|
||||
BlueprintIdentifier = "88A3FB45182E079E00705327"
|
||||
BuildableName = "JSMessagesDemo.app"
|
||||
BlueprintName = "JSMessagesDemo"
|
||||
ReferencedContainer = "container:JSMessagesDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
1
JSMessagesDemo.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?><Workspace version='1.0'><FileRef location='group:JSMessagesDemo.xcodeproj'/><FileRef location='group:Pods/Pods.xcodeproj'/></Workspace>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,75 +1,48 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small@2x.png",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-87.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Spotlight-iOS7@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-120-1.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-120.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-180.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Spotlight-iOS7.png",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Spotlight-iOS7@2x.png",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76.png",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76@2x.png",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
@ -3,17 +3,12 @@
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x",
|
||||
"filename" : "goldengate.png"
|
||||
"filename" : "demo-avatar-cook.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "goldengate@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x",
|
||||
"filename" : "goldengate@3x.png"
|
||||
"filename" : "demo-avatar-cook@2x.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
BIN
JSMessagesDemo/Images.xcassets/DemoAvatars/demo-avatar-cook.imageset/demo-avatar-cook.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
JSMessagesDemo/Images.xcassets/DemoAvatars/demo-avatar-cook.imageset/demo-avatar-cook@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 96 KiB |
@ -3,17 +3,12 @@
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x",
|
||||
"filename" : "demo_avatar_woz.png"
|
||||
"filename" : "demo-avatar-jobs.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "demo_avatar_woz@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x",
|
||||
"filename" : "demo_avatar_woz@3x.png"
|
||||
"filename" : "demo-avatar-jobs@2x.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
BIN
JSMessagesDemo/Images.xcassets/DemoAvatars/demo-avatar-jobs.imageset/demo-avatar-jobs.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
JSMessagesDemo/Images.xcassets/DemoAvatars/demo-avatar-jobs.imageset/demo-avatar-jobs@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 42 KiB |
@ -3,17 +3,12 @@
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x",
|
||||
"filename" : "demo_avatar_cook.png"
|
||||
"filename" : "demo-avatar-woz.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "demo_avatar_cook@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x",
|
||||
"filename" : "demo_avatar_cook@3x.png"
|
||||
"filename" : "demo-avatar-woz@2x.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
BIN
JSMessagesDemo/Images.xcassets/DemoAvatars/demo-avatar-woz.imageset/demo-avatar-woz.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
JSMessagesDemo/Images.xcassets/DemoAvatars/demo-avatar-woz.imageset/demo-avatar-woz@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 91 KiB |
@ -1,47 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "736h",
|
||||
"filename" : "jsq_messages_splash_55inch.png",
|
||||
"minimum-system-version" : "8.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "736h",
|
||||
"filename" : "jsq_messages_splash_55inch_landscape.png",
|
||||
"minimum-system-version" : "8.0",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "667h",
|
||||
"filename" : "jsq_messages_splash_47inch.png",
|
||||
"minimum-system-version" : "8.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "7.0",
|
||||
"filename" : "splash_iphone35inch.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"filename" : "slpash_iphone4inch.png",
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
@ -49,7 +20,6 @@
|
||||
"idiom" : "ipad",
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "7.0",
|
||||
"filename" : "jsq_messages_splash_ipad.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
@ -57,7 +27,6 @@
|
||||
"idiom" : "ipad",
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "7.0",
|
||||
"filename" : "jsq_messages_splash_ipad~landscape.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
@ -65,7 +34,6 @@
|
||||
"idiom" : "ipad",
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "7.0",
|
||||
"filename" : "jsq_messages_splash_ipad@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
@ -73,7 +41,6 @@
|
||||
"idiom" : "ipad",
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "7.0",
|
||||
"filename" : "jsq_messages_splash_ipad@2x~landscape.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
21
JSMessagesDemo/JSAppDelegate.h
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface JSAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
|
||||
@end
|
||||
32
JSMessagesDemo/JSAppDelegate.m
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSAppDelegate.h"
|
||||
#import "JSDemoViewController.h"
|
||||
|
||||
@implementation JSAppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
JSDemoViewController *vc = [[JSDemoViewController alloc] init];
|
||||
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
self.window.rootViewController = nc;
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
22
JSMessagesDemo/JSDemoViewController.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSMessagesViewController.h"
|
||||
|
||||
@interface JSDemoViewController : JSMessagesViewController <JSMessagesViewDataSource, JSMessagesViewDelegate>
|
||||
|
||||
@property (strong, nonatomic) NSMutableArray *messages;
|
||||
@property (strong, nonatomic) NSDictionary *avatars;
|
||||
|
||||
@end
|
||||
204
JSMessagesDemo/JSDemoViewController.m
Normal file
@ -0,0 +1,204 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSDemoViewController.h"
|
||||
#import "JSMessage.h"
|
||||
|
||||
#define kSubtitleJobs @"Jobs"
|
||||
#define kSubtitleWoz @"Steve Wozniak"
|
||||
#define kSubtitleCook @"Mr. Cook"
|
||||
|
||||
@implementation JSDemoViewController
|
||||
|
||||
#pragma mark - View lifecycle
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
self.delegate = self;
|
||||
self.dataSource = self;
|
||||
[super viewDidLoad];
|
||||
|
||||
[[JSBubbleView appearance] setFont:[UIFont systemFontOfSize:16.0f]];
|
||||
|
||||
self.title = @"Messages";
|
||||
self.messageInputView.textView.placeHolder = @"New Message";
|
||||
self.sender = @"Jobs";
|
||||
|
||||
[self setBackgroundColor:[UIColor whiteColor]];
|
||||
|
||||
self.messages = [[NSMutableArray alloc] initWithObjects:
|
||||
[[JSMessage alloc] initWithText:@"JSMessagesViewController is simple and easy to use." sender:kSubtitleJobs date:[NSDate distantPast]],
|
||||
[[JSMessage alloc] initWithText:@"It's highly customizable." sender:kSubtitleWoz date:[NSDate distantPast]],
|
||||
[[JSMessage alloc] initWithText:@"It even has data detectors. You can call me tonight. My cell number is 452-123-4567. \nMy website is www.hexedbits.com." sender:kSubtitleJobs date:[NSDate distantPast]],
|
||||
[[JSMessage alloc] initWithText:@"Group chat. Sound effects and images included. Animations are smooth. Messages can be of arbitrary size!" sender:kSubtitleCook date:[NSDate distantPast]],
|
||||
[[JSMessage alloc] initWithText:@"Group chat. Sound effects and images included. Animations are smooth. Messages can be of arbitrary size!" sender:kSubtitleJobs date:[NSDate date]],
|
||||
[[JSMessage alloc] initWithText:@"Group chat. Sound effects and images included. Animations are smooth. Messages can be of arbitrary size!" sender:kSubtitleWoz date:[NSDate date]],
|
||||
nil];
|
||||
|
||||
|
||||
for (NSUInteger i = 0; i < 3; i++) {
|
||||
[self.messages addObjectsFromArray:self.messages];
|
||||
}
|
||||
|
||||
self.avatars = [[NSDictionary alloc] initWithObjectsAndKeys:
|
||||
[JSAvatarImageFactory avatarImageNamed:@"demo-avatar-jobs" croppedToCircle:YES], kSubtitleJobs,
|
||||
[JSAvatarImageFactory avatarImageNamed:@"demo-avatar-woz" croppedToCircle:YES], kSubtitleWoz,
|
||||
[JSAvatarImageFactory avatarImageNamed:@"demo-avatar-cook" croppedToCircle:YES], kSubtitleCook,
|
||||
nil];
|
||||
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFastForward
|
||||
target:self
|
||||
action:@selector(buttonPressed:)];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
[self scrollToBottomAnimated:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)buttonPressed:(UIBarButtonItem *)sender
|
||||
{
|
||||
// Testing pushing/popping messages view
|
||||
JSDemoViewController *vc = [[JSDemoViewController alloc] initWithNibName:nil bundle:nil];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return self.messages.count;
|
||||
}
|
||||
|
||||
#pragma mark - Messages view delegate: REQUIRED
|
||||
|
||||
- (void)didSendText:(NSString *)text fromSender:(NSString *)sender onDate:(NSDate *)date
|
||||
{
|
||||
if ((self.messages.count - 1) % 2) {
|
||||
[JSMessageSoundEffect playMessageSentSound];
|
||||
}
|
||||
else {
|
||||
// for demo purposes only, mimicing received messages
|
||||
[JSMessageSoundEffect playMessageReceivedSound];
|
||||
sender = arc4random_uniform(10) % 2 ? kSubtitleCook : kSubtitleWoz;
|
||||
}
|
||||
|
||||
[self.messages addObject:[[JSMessage alloc] initWithText:text sender:sender date:date]];
|
||||
|
||||
[self finishSend];
|
||||
[self scrollToBottomAnimated:YES];
|
||||
}
|
||||
|
||||
- (JSBubbleMessageType)messageTypeForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return (indexPath.row % 2) ? JSBubbleMessageTypeIncoming : JSBubbleMessageTypeOutgoing;
|
||||
}
|
||||
|
||||
- (UIImageView *)bubbleImageViewWithType:(JSBubbleMessageType)type
|
||||
forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.row % 2) {
|
||||
return [JSBubbleImageViewFactory bubbleImageViewForType:type
|
||||
color:[UIColor js_bubbleLightGrayColor]];
|
||||
}
|
||||
|
||||
return [JSBubbleImageViewFactory bubbleImageViewForType:type
|
||||
color:[UIColor js_bubbleBlueColor]];
|
||||
}
|
||||
|
||||
- (JSMessageInputViewStyle)inputViewStyle
|
||||
{
|
||||
return JSMessageInputViewStyleFlat;
|
||||
}
|
||||
|
||||
#pragma mark - Messages view delegate: OPTIONAL
|
||||
|
||||
- (BOOL)shouldDisplayTimestampForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.row % 3 == 0) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
//
|
||||
// *** Implement to customize cell further
|
||||
//
|
||||
- (void)configureCell:(JSBubbleMessageCell *)cell atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([cell messageType] == JSBubbleMessageTypeOutgoing) {
|
||||
cell.bubbleView.textView.textColor = [UIColor whiteColor];
|
||||
|
||||
if ([cell.bubbleView.textView respondsToSelector:@selector(linkTextAttributes)]) {
|
||||
NSMutableDictionary *attrs = [cell.bubbleView.textView.linkTextAttributes mutableCopy];
|
||||
[attrs setValue:[UIColor blueColor] forKey:UITextAttributeTextColor];
|
||||
|
||||
cell.bubbleView.textView.linkTextAttributes = attrs;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.timestampLabel) {
|
||||
cell.timestampLabel.textColor = [UIColor lightGrayColor];
|
||||
cell.timestampLabel.shadowOffset = CGSizeZero;
|
||||
}
|
||||
|
||||
if (cell.subtitleLabel) {
|
||||
cell.subtitleLabel.textColor = [UIColor lightGrayColor];
|
||||
}
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
cell.bubbleView.textView.dataDetectorTypes = UIDataDetectorTypeNone;
|
||||
#else
|
||||
cell.bubbleView.textView.dataDetectorTypes = UIDataDetectorTypeAll;
|
||||
#endif
|
||||
}
|
||||
|
||||
// *** Implement to use a custom send button
|
||||
//
|
||||
// The button's frame is set automatically for you
|
||||
//
|
||||
// - (UIButton *)sendButtonForInputView
|
||||
//
|
||||
|
||||
// *** Implement to prevent auto-scrolling when message is added
|
||||
//
|
||||
- (BOOL)shouldPreventScrollToBottomWhileUserScrolling
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// *** Implemnt to enable/disable pan/tap todismiss keyboard
|
||||
//
|
||||
- (BOOL)allowsPanToDismissKeyboard
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Messages view data source: REQUIRED
|
||||
|
||||
- (JSMessage *)messageForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [self.messages objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
- (UIImageView *)avatarImageViewForRowAtIndexPath:(NSIndexPath *)indexPath sender:(NSString *)sender
|
||||
{
|
||||
UIImage *image = [self.avatars objectForKey:sender];
|
||||
return [[UIImageView alloc] initWithImage:image];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -4,28 +4,26 @@
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.hexedbits.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<string>com.hexedbits.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>6.1.1</string>
|
||||
<string>4.0.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>6.1.1</string>
|
||||
<string>4.0.4</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
@ -33,15 +31,11 @@
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
19
JSMessagesDemo/JSMessagesDemo-Prefix.pch
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Prefix header
|
||||
//
|
||||
// The contents of this file are implicitly included at the beginning of every source file.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
|
||||
#ifndef __IPHONE_5_0
|
||||
#warning "This project uses features only available in iOS SDK 5.0 and later."
|
||||
#endif
|
||||
|
||||
// Make NSLog more useful and pretty
|
||||
#define NSLog(__FORMAT__, ...) NSLog((@"%s [Line %d] " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
2
JSMessagesDemo/en.lproj/InfoPlist.strings
Normal file
@ -0,0 +1,2 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
18
JSMessagesDemo/main.m
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// main.m
|
||||
// JSMessagesDemo
|
||||
//
|
||||
// Created by Jesse Squires on 11/8/13.
|
||||
// Copyright (c) 2013 Hexed Bits. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "JSAppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([JSAppDelegate class]));
|
||||
}
|
||||
}
|
||||
@ -5,13 +5,11 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.hexedbits.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<string>com.hexedbits.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
43
JSMessagesDemoTests/JSMessagesDemoTests.m
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "JSMessagesViewController.h"
|
||||
|
||||
|
||||
@interface JSMessagesDemoTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation JSMessagesDemoTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testInit
|
||||
{
|
||||
JSMessagesViewController *vc = [[JSMessagesViewController alloc] init];
|
||||
XCTAssertNotNil(vc, @"View controller should not be nil");
|
||||
}
|
||||
|
||||
@end
|
||||
2
JSMessagesDemoTests/en.lproj/InfoPlist.strings
Normal file
@ -0,0 +1,2 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
17
JSMessagesViewController.podspec
Normal file
@ -0,0 +1,17 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'JSMessagesViewController'
|
||||
s.version = '4.0.4'
|
||||
s.summary = 'A messages UI for iPhone and iPad.'
|
||||
s.homepage = 'https://github.com/jessesquires/MessagesTableViewController'
|
||||
s.social_media_url = 'https://twitter.com/jesse_squires'
|
||||
s.license = 'MIT'
|
||||
s.authors = { 'Jesse Squires' => 'jesse.squires.developer@gmail.com' }
|
||||
s.source = { :git => 'https://github.com/jessesquires/MessagesTableViewController.git', :tag => s.version.to_s }
|
||||
s.platform = :ios, '6.0'
|
||||
s.source_files = 'JSMessagesViewController/Classes/**/*'
|
||||
s.resources = 'JSMessagesViewController/Resources/**/**/*'
|
||||
s.frameworks = 'QuartzCore'
|
||||
s.requires_arc = true
|
||||
|
||||
s.dependency 'JSQSystemSoundPlayer', '1.5.2'
|
||||
end
|
||||
@ -0,0 +1,33 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSString (JSMessagesView)
|
||||
|
||||
/**
|
||||
* Returns a copy of the receiver with all whitespaced removed from the front and back.
|
||||
*
|
||||
* @return A copied string with all leading and trailing whitespace removed.
|
||||
*/
|
||||
- (NSString *)js_stringByTrimingWhitespace;
|
||||
|
||||
/**
|
||||
* Returns the number of lines in the receiver by counting the number of occurences of the newline character, `\n`.
|
||||
*
|
||||
* @return An unsigned integer describing the number of lines in the string.
|
||||
*/
|
||||
- (NSUInteger)js_numberOfLines;
|
||||
|
||||
@end
|
||||
@ -0,0 +1,29 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "NSString+JSMessagesView.h"
|
||||
|
||||
@implementation NSString (JSMessagesView)
|
||||
|
||||
- (NSString *)js_stringByTrimingWhitespace
|
||||
{
|
||||
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
}
|
||||
|
||||
- (NSUInteger)js_numberOfLines
|
||||
{
|
||||
return [[self componentsSeparatedByString:@"\n"] count] + 1;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -0,0 +1,59 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIColor (JSMessagesView)
|
||||
|
||||
#pragma mark - Colors
|
||||
|
||||
/**
|
||||
* @return A color object containing the RGB values for the iOS 6 messages app background color.
|
||||
*/
|
||||
+ (UIColor *)js_backgroundColorClassic;
|
||||
|
||||
/**
|
||||
* @return A color object containing the RGB values iOS 6 messages app timestamp text color.
|
||||
*/
|
||||
+ (UIColor *)js_messagesTimestampColorClassic;
|
||||
|
||||
#pragma mark - Bubble colors
|
||||
|
||||
/**
|
||||
* @return A color object containing the HSB values iOS 7 messages app green bubble color.
|
||||
*/
|
||||
+ (UIColor *)js_bubbleGreenColor;
|
||||
|
||||
/**
|
||||
* @return A color object containing the HSB values iOS 7 messages app blue bubble color.
|
||||
*/
|
||||
+ (UIColor *)js_bubbleBlueColor;
|
||||
|
||||
/**
|
||||
* @return A color object containing the HSB values iOS 7 messages app light gray bubble color.
|
||||
*/
|
||||
+ (UIColor *)js_bubbleLightGrayColor;
|
||||
|
||||
#pragma mark - Utilities
|
||||
|
||||
/**
|
||||
* Creates and returns a new color object whose brightness component is decreased by the given value, using the initial color values of the receiver.
|
||||
*
|
||||
* @param value A floating point value describing the amount by which to decrease the brightness of the receiver.
|
||||
*
|
||||
* @return A new color object whose brightness is decreased by the given values. The other color values remain the same as the receiver.
|
||||
*/
|
||||
- (UIColor *)js_darkenColorWithValue:(CGFloat)value;
|
||||
|
||||
@end
|
||||
@ -1,36 +1,44 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.jessesquires.com
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSQMessagesViewController
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// GitHub
|
||||
// https://github.com/jessesquires/JSQMessagesViewController
|
||||
//
|
||||
//
|
||||
// License
|
||||
// Copyright (c) 2014 Jesse Squires
|
||||
// Released under an MIT license: http://opensource.org/licenses/MIT
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "UIColor+JSQMessages.h"
|
||||
#import "UIColor+JSMessagesView.h"
|
||||
|
||||
@implementation UIColor (JSQMessages)
|
||||
@implementation UIColor (JSMessagesView)
|
||||
|
||||
#pragma mark - Message bubble colors
|
||||
#pragma mark - Colors
|
||||
|
||||
+ (UIColor *)jsq_messageBubbleGreenColor
|
||||
+ (UIColor *)js_backgroundColorClassic
|
||||
{
|
||||
return [UIColor colorWithRed:0.859f green:0.886f blue:0.929f alpha:1.0f];
|
||||
}
|
||||
|
||||
+ (UIColor *)js_messagesTimestampColorClassic
|
||||
{
|
||||
return [UIColor colorWithRed:0.533f green:0.573f blue:0.647f alpha:1.0f];
|
||||
}
|
||||
|
||||
#pragma mark - Bubble colors
|
||||
|
||||
+ (UIColor *)js_bubbleGreenColor
|
||||
{
|
||||
return [UIColor colorWithHue:130.0f / 360.0f
|
||||
saturation:0.68f
|
||||
brightness:0.84f
|
||||
brightness:0.80f
|
||||
alpha:1.0f];
|
||||
}
|
||||
|
||||
+ (UIColor *)jsq_messageBubbleBlueColor
|
||||
+ (UIColor *)js_bubbleBlueColor
|
||||
{
|
||||
return [UIColor colorWithHue:210.0f / 360.0f
|
||||
saturation:0.94f
|
||||
@ -38,15 +46,7 @@
|
||||
alpha:1.0f];
|
||||
}
|
||||
|
||||
+ (UIColor *)jsq_messageBubbleRedColor
|
||||
{
|
||||
return [UIColor colorWithHue:0.0f / 360.0f
|
||||
saturation:0.79f
|
||||
brightness:1.0f
|
||||
alpha:1.0f];
|
||||
}
|
||||
|
||||
+ (UIColor *)jsq_messageBubbleLightGrayColor
|
||||
+ (UIColor *)js_bubbleLightGrayColor
|
||||
{
|
||||
return [UIColor colorWithHue:240.0f / 360.0f
|
||||
saturation:0.02f
|
||||
@ -54,26 +54,9 @@
|
||||
alpha:1.0f];
|
||||
}
|
||||
|
||||
+ (UIColor *)jsq_messageBubbleLightBlueColor
|
||||
{
|
||||
return [UIColor colorWithRed:137/255.f green:214/255.f blue:250/255.f alpha:1];
|
||||
}
|
||||
|
||||
+ (UIColor *)jsq_messageBubbleGrayColor
|
||||
{
|
||||
return [UIColor colorWithRed:230/255.f green:230/255.f blue:230/255.f alpha:1];
|
||||
}
|
||||
|
||||
+ (UIColor *)jsq_messageBackgroundGrayColor
|
||||
{
|
||||
return [UIColor colorWithRed:242.f/255 green:242.f/255 blue:242.f/255 alpha:1];
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - Utilities
|
||||
|
||||
- (UIColor *)jsq_colorByDarkeningColorWithValue:(CGFloat)value
|
||||
- (UIColor *)js_darkenColorWithValue:(CGFloat)value
|
||||
{
|
||||
NSUInteger totalComponents = CGColorGetNumberOfComponents(self.CGColor);
|
||||
BOOL isGreyscale = (totalComponents == 2) ? YES : NO;
|
||||
@ -0,0 +1,63 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIImage (JSMessagesView)
|
||||
|
||||
/**
|
||||
* Creates and returns an image object that is a copy of the receiver and flipped horizontally.
|
||||
*
|
||||
* @return A new image object.
|
||||
*/
|
||||
- (UIImage *)js_imageFlippedHorizontal;
|
||||
|
||||
/**
|
||||
* Creates and returns a new stretchable image object with the specified cap insets.
|
||||
*
|
||||
* @param capInsets The values to use for the cap insets.
|
||||
*
|
||||
* @return A new image object with the specified cap insets and mode `UIImageResizingModeStretch`.
|
||||
*/
|
||||
- (UIImage *)js_stretchableImageWithCapInsets:(UIEdgeInsets)capInsets;
|
||||
|
||||
/**
|
||||
* Creates and returns a new image object with the specified attributes.
|
||||
*
|
||||
* @param clipToCircle A boolean value indicating whether or not the returned image should be cropped to a circle. Pass `YES` to crop the returned image to a circle, and `NO` to crop as a square.
|
||||
* @param diameter A floating point value indicating the diamater of the returned image.
|
||||
* @param borderColor The color with which to stroke the returned image.
|
||||
* @param borderWidth The width of the border of the returned image.
|
||||
* @param shadowOffset The values for the shadow offset of the returned image.
|
||||
*
|
||||
* @return A new image object with the specified attributes.
|
||||
*
|
||||
* @warning This method crops a copy of the receiver into a perfect square centered on the center point of the image before applying the other attributes.
|
||||
*/
|
||||
- (UIImage *)js_imageAsCircle:(BOOL)clipToCircle
|
||||
withDiamter:(CGFloat)diameter
|
||||
borderColor:(UIColor *)borderColor
|
||||
borderWidth:(CGFloat)borderWidth
|
||||
shadowOffSet:(CGSize)shadowOffset;
|
||||
|
||||
/**
|
||||
* Creates and returns a new image object that is masked with the specified mask color.
|
||||
*
|
||||
* @param maskColor The color value for the mask.
|
||||
*
|
||||
* @return A new image object masked with the specified color.
|
||||
*/
|
||||
- (UIImage *)js_imageMaskWithColor:(UIColor *)maskColor;
|
||||
|
||||
@end
|
||||
@ -0,0 +1,111 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "UIImage+JSMessagesView.h"
|
||||
|
||||
@implementation UIImage (JSMessagesView)
|
||||
|
||||
- (UIImage *)js_imageFlippedHorizontal
|
||||
{
|
||||
return [UIImage imageWithCGImage:self.CGImage
|
||||
scale:self.scale
|
||||
orientation:UIImageOrientationUpMirrored];
|
||||
}
|
||||
|
||||
- (UIImage *)js_stretchableImageWithCapInsets:(UIEdgeInsets)capInsets
|
||||
{
|
||||
return [self resizableImageWithCapInsets:capInsets
|
||||
resizingMode:UIImageResizingModeStretch];
|
||||
}
|
||||
|
||||
- (UIImage *)js_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.1f;
|
||||
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),
|
||||
2.0f,
|
||||
[UIColor colorWithWhite:0.0f alpha:0.45f].CGColor);
|
||||
}
|
||||
|
||||
// draw border
|
||||
if (borderColor && borderWidth) {
|
||||
CGPathRef borderPath = (clipToCircle) ? CGPathCreateWithEllipseInRect(imgRect, NULL) : CGPathCreateWithRect(imgRect, NULL);
|
||||
CGContextAddPath(context, borderPath);
|
||||
|
||||
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
|
||||
CGContextSetLineWidth(context, borderWidth);
|
||||
CGContextDrawPath(context, kCGPathFillStroke);
|
||||
|
||||
CGPathRelease(borderPath);
|
||||
}
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
if (clipToCircle) {
|
||||
UIBezierPath *imgPath = [UIBezierPath bezierPathWithOvalInRect:imgRect];
|
||||
[imgPath addClip];
|
||||
}
|
||||
|
||||
[self drawInRect:imgRect];
|
||||
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
||||
- (UIImage *)js_imageMaskWithColor:(UIColor *)maskColor
|
||||
{
|
||||
CGRect imageRect = CGRectMake(0.0f, 0.0f, self.size.width, self.size.height);
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, self.scale);
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextScaleCTM(ctx, 1.0f, -1.0f);
|
||||
CGContextTranslateCTM(ctx, 0.0f, -(imageRect.size.height));
|
||||
|
||||
CGContextClipToMask(ctx, imageRect, self.CGImage);
|
||||
CGContextSetFillColorWithColor(ctx, maskColor.CGColor);
|
||||
CGContextFillRect(ctx, imageRect);
|
||||
|
||||
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -0,0 +1,60 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* A constant defining the size of an avatar image.
|
||||
*/
|
||||
extern CGFloat const kJSAvatarImageSize;
|
||||
|
||||
/**
|
||||
* `JSAvatarImageFactory` is a factory that provides a means for styling avatar images to be displayed in a `JSBubbleMessageCell` of a `JSMessagesViewController`.
|
||||
*/
|
||||
@interface JSAvatarImageFactory : NSObject
|
||||
|
||||
/**
|
||||
* Returns the image object associated with the specified filename. The image is cropped to a circle if the value of croppedToCircle is `YES`, otherwise the image is cropped to a square. The image has a flat, iOS 7 appearance.
|
||||
*
|
||||
* @param filename The name of the file. If this is the first time the image is being loaded, the method looks for an image with the specified name in the application’s main bundle.
|
||||
* @param croppedToCircle A boolean value indicating whether or not the image should be cropped as a circle or square. Pass `YES` to crop to a circle, and `NO` to crop to a square.
|
||||
*
|
||||
* @return The image object for the specified file (cropped as specified), or `nil` if the method could not find the specified image.
|
||||
*/
|
||||
+ (UIImage *)avatarImageNamed:(NSString *)filename
|
||||
croppedToCircle:(BOOL)croppedToCircle;
|
||||
|
||||
/**
|
||||
* Returns a copy of the image object associated with the specified originalImage. The image is cropped to a circle if the value of croppedToCircle is `YES`, otherwise the image is cropped to a square. The image has a flat, iOS 7 appearance.
|
||||
*
|
||||
* @param originalImage The origin image object to be styled for an avatar.
|
||||
* @param croppedToCircle A boolean value indicating whether or not the image should be cropped as a circle or square. Pass `YES` to crop to a circle, and `NO` to crop to a square.
|
||||
*
|
||||
* @return A new image object for the specified originalImage (cropped as specified), or `nil` if originalImage is not a valid, initialized image object.
|
||||
*/
|
||||
+ (UIImage *)avatarImage:(UIImage *)originalImage
|
||||
croppedToCircle:(BOOL)croppedToCircle;
|
||||
|
||||
/**
|
||||
* Returns the image object associated with the specified filename. The image is cropped to a circle if the value of croppedToCircle is `YES`, otherwise the image is cropped to a square. The image has a glossy, iOS 6 appearance.
|
||||
*
|
||||
* @param filename The name of the file. If this is the first time the image is being loaded, the method looks for an image with the specified name in the application’s main bundle.
|
||||
* @param croppedToCircle A boolean value indicating whether or not the image should be cropped as a circle or square. Pass `YES` to crop to a circle, and `NO` to crop to a square.
|
||||
*
|
||||
* @return The image object for the specified file (cropped as specified), or `nil` if the method could not find the specified image.
|
||||
*/
|
||||
+ (UIImage *)classicAvatarImageNamed:(NSString *)filename
|
||||
croppedToCircle:(BOOL)croppedToCircle;
|
||||
|
||||
@end
|
||||
@ -0,0 +1,50 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSAvatarImageFactory.h"
|
||||
#import "UIImage+JSMessagesView.h"
|
||||
|
||||
CGFloat const kJSAvatarImageSize = 50.0f;
|
||||
|
||||
@implementation JSAvatarImageFactory
|
||||
|
||||
+ (UIImage *)avatarImageNamed:(NSString *)filename
|
||||
croppedToCircle:(BOOL)croppedToCircle
|
||||
{
|
||||
UIImage *image = [UIImage imageNamed:filename];
|
||||
return [self avatarImage:image croppedToCircle:croppedToCircle];
|
||||
}
|
||||
|
||||
+ (UIImage *)avatarImage:(UIImage *)originalImage
|
||||
croppedToCircle:(BOOL)croppedToCircle
|
||||
{
|
||||
return [originalImage js_imageAsCircle:croppedToCircle
|
||||
withDiamter:kJSAvatarImageSize
|
||||
borderColor:nil
|
||||
borderWidth:0.0f
|
||||
shadowOffSet:CGSizeZero];
|
||||
}
|
||||
|
||||
+ (UIImage *)classicAvatarImageNamed:(NSString *)filename
|
||||
croppedToCircle:(BOOL)croppedToCircle
|
||||
{
|
||||
UIImage *image = [UIImage imageNamed:filename];
|
||||
return [image js_imageAsCircle:croppedToCircle
|
||||
withDiamter:kJSAvatarImageSize
|
||||
borderColor:[UIColor colorWithHue:0.0f saturation:0.0f brightness:0.8f alpha:1.0f]
|
||||
borderWidth:1.0f
|
||||
shadowOffSet:CGSizeMake(0.0f, 1.0f)];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -0,0 +1,86 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* The type of bubble for a `JSBubbleMessageCell` object.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, JSBubbleMessageType) {
|
||||
/**
|
||||
* Specifies an incoming, or received message.
|
||||
*/
|
||||
JSBubbleMessageTypeIncoming,
|
||||
/**
|
||||
* Specifies an outgoing, or sent message.
|
||||
*/
|
||||
JSBubbleMessageTypeOutgoing
|
||||
};
|
||||
|
||||
/**
|
||||
* The style of a classic bubble image with an iOS 6 appearance.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, JSBubbleImageViewStyle) {
|
||||
/**
|
||||
* Specifies a glossy gray messsage bubble.
|
||||
*/
|
||||
JSBubbleImageViewStyleClassicGray,
|
||||
/**
|
||||
* Specifies a glossy blue messsage bubble.
|
||||
*/
|
||||
JSBubbleImageViewStyleClassicBlue,
|
||||
/**
|
||||
* Specifies a glossy green messsage bubble.
|
||||
*/
|
||||
JSBubbleImageViewStyleClassicGreen,
|
||||
/**
|
||||
* Specifies a glossy gray square messsage bubble.
|
||||
*/
|
||||
JSBubbleImageViewStyleClassicSquareGray,
|
||||
/**
|
||||
* Specifies a glossy blue square messsage bubble.
|
||||
*/
|
||||
JSBubbleImageViewStyleClassicSquareBlue
|
||||
};
|
||||
|
||||
/**
|
||||
* `JSBubbleImageViewFactory` is a factory that provides a means for styling bubble image views to be displayed in a `JSBubbleMessageCell` of a `JSMessagesViewController`.
|
||||
*/
|
||||
@interface JSBubbleImageViewFactory : NSObject
|
||||
|
||||
/**
|
||||
* Creates and returns an image view object with the specified type and color. The `image` property of the image view is configured with a flat, iOS7-style bubble image, masked with the given color. The `highlightedImage` property is configured similarly, but with a slightly darkened version of the given color.
|
||||
*
|
||||
* @param type The type of the bubble image view.
|
||||
* @param color The color of the bubble image in the image view.
|
||||
*
|
||||
* @return An initialized image view object if created successfully, `nil` otherwise.
|
||||
*/
|
||||
+ (UIImageView *)bubbleImageViewForType:(JSBubbleMessageType)type
|
||||
color:(UIColor *)color;
|
||||
|
||||
/**
|
||||
* Creates and returns an image view object with the specified type and style. The `image` property of the image view is configured with a glossy, iOS6-style bubble image, corresponding to the given style. The `highlightedImage` property is configured similarly, but with a selected version of the bubble image.
|
||||
*
|
||||
* @param type The type of the bubble image view.
|
||||
* @param style The style of the bubble image in the image view.
|
||||
*
|
||||
* @see `JSBubbleImageViewStyle`.
|
||||
*
|
||||
* @return An initialized image view object if created successfully, `nil` otherwise.
|
||||
*/
|
||||
+ (UIImageView *)classicBubbleImageViewForType:(JSBubbleMessageType)type
|
||||
style:(JSBubbleImageViewStyle)style;
|
||||
|
||||
@end
|
||||
@ -0,0 +1,138 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSBubbleImageViewFactory.h"
|
||||
#import "UIImage+JSMessagesView.h"
|
||||
#import "UIColor+JSMessagesView.h"
|
||||
|
||||
static NSDictionary *bubbleImageDictionary;
|
||||
|
||||
@interface JSBubbleImageViewFactory()
|
||||
|
||||
+ (UIImageView *)classicBubbleImageViewForStyle:(JSBubbleImageViewStyle)style
|
||||
isOutgoing:(BOOL)isOutgoing;
|
||||
|
||||
+ (UIImage *)classicHighlightedBubbleImageForStyle:(JSBubbleImageViewStyle)style;
|
||||
|
||||
+ (UIEdgeInsets)classicBubbleImageCapInsetsForStyle:(JSBubbleImageViewStyle)style
|
||||
isOutgoing:(BOOL)isOutgoing;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation JSBubbleImageViewFactory
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
bubbleImageDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"bubble-classic-gray", @(JSBubbleImageViewStyleClassicGray),
|
||||
@"bubble-classic-blue", @(JSBubbleImageViewStyleClassicBlue),
|
||||
@"bubble-classic-green", @(JSBubbleImageViewStyleClassicGreen),
|
||||
@"bubble-classic-square-gray", @(JSBubbleImageViewStyleClassicSquareGray),
|
||||
@"bubble-classic-square-blue", @(JSBubbleImageViewStyleClassicSquareBlue),
|
||||
nil];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
+ (UIImageView *)bubbleImageViewForType:(JSBubbleMessageType)type
|
||||
color:(UIColor *)color
|
||||
{
|
||||
UIImage *bubble = [UIImage imageNamed:@"bubble-min"];
|
||||
|
||||
UIImage *normalBubble = [bubble js_imageMaskWithColor:color];
|
||||
UIImage *highlightedBubble = [bubble js_imageMaskWithColor:[color js_darkenColorWithValue:0.12f]];
|
||||
|
||||
if (type == JSBubbleMessageTypeIncoming) {
|
||||
normalBubble = [normalBubble js_imageFlippedHorizontal];
|
||||
highlightedBubble = [highlightedBubble js_imageFlippedHorizontal];
|
||||
}
|
||||
|
||||
// make image stretchable from center point
|
||||
CGPoint center = CGPointMake(bubble.size.width / 2.0f, bubble.size.height / 2.0f);
|
||||
UIEdgeInsets capInsets = UIEdgeInsetsMake(center.y, center.x, center.y, center.x);
|
||||
|
||||
return [[UIImageView alloc] initWithImage:[normalBubble js_stretchableImageWithCapInsets:capInsets]
|
||||
highlightedImage:[highlightedBubble js_stretchableImageWithCapInsets:capInsets]];
|
||||
}
|
||||
|
||||
+ (UIImageView *)classicBubbleImageViewForType:(JSBubbleMessageType)type
|
||||
style:(JSBubbleImageViewStyle)style
|
||||
{
|
||||
return [JSBubbleImageViewFactory classicBubbleImageViewForStyle:style
|
||||
isOutgoing:type == JSBubbleMessageTypeOutgoing];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
+ (UIImageView *)classicBubbleImageViewForStyle:(JSBubbleImageViewStyle)style
|
||||
isOutgoing:(BOOL)isOutgoing
|
||||
{
|
||||
UIImage *image = [UIImage imageNamed:[bubbleImageDictionary objectForKey:@(style)]];
|
||||
UIImage *highlightedImage = [JSBubbleImageViewFactory classicHighlightedBubbleImageForStyle:style];
|
||||
|
||||
if (!isOutgoing) {
|
||||
image = [image js_imageFlippedHorizontal];
|
||||
highlightedImage = [highlightedImage js_imageFlippedHorizontal];
|
||||
}
|
||||
|
||||
UIEdgeInsets capInsets = [JSBubbleImageViewFactory classicBubbleImageCapInsetsForStyle:style
|
||||
isOutgoing:isOutgoing];
|
||||
|
||||
return [[UIImageView alloc] initWithImage:[image js_stretchableImageWithCapInsets:capInsets]
|
||||
highlightedImage:[highlightedImage js_stretchableImageWithCapInsets:capInsets]];
|
||||
}
|
||||
|
||||
+ (UIImage *)classicHighlightedBubbleImageForStyle:(JSBubbleImageViewStyle)style
|
||||
{
|
||||
switch (style) {
|
||||
case JSBubbleImageViewStyleClassicGray:
|
||||
case JSBubbleImageViewStyleClassicBlue:
|
||||
case JSBubbleImageViewStyleClassicGreen:
|
||||
return [UIImage imageNamed:@"bubble-classic-selected"];
|
||||
|
||||
case JSBubbleImageViewStyleClassicSquareGray:
|
||||
case JSBubbleImageViewStyleClassicSquareBlue:
|
||||
return [UIImage imageNamed:@"bubble-classic-square-selected"];
|
||||
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (UIEdgeInsets)classicBubbleImageCapInsetsForStyle:(JSBubbleImageViewStyle)style
|
||||
isOutgoing:(BOOL)isOutgoing
|
||||
{
|
||||
switch (style) {
|
||||
case JSBubbleImageViewStyleClassicGray:
|
||||
case JSBubbleImageViewStyleClassicBlue:
|
||||
case JSBubbleImageViewStyleClassicGreen:
|
||||
return UIEdgeInsetsMake(15.0f, 20.0f, 15.0f, 20.0f);
|
||||
|
||||
case JSBubbleImageViewStyleClassicSquareGray:
|
||||
case JSBubbleImageViewStyleClassicSquareBlue:
|
||||
return isOutgoing ? UIEdgeInsetsMake(15.0f, 18.0f, 16.0f, 23.0f) : UIEdgeInsetsMake(15.0f, 25.0f, 16.0f, 23.0f);
|
||||
|
||||
default:
|
||||
return UIEdgeInsetsZero;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
111
JSMessagesViewController/Classes/JSBubbleMessageCell.h
Normal file
@ -0,0 +1,111 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JSBubbleView.h"
|
||||
#import "JSMessageData.h"
|
||||
|
||||
/**
|
||||
* The `JSBubbleMessageCell` class defines the attributes and behavior of the cells that appear in `JSMessagesViewController`. This class includes properties and methods for setting and managing cell content.
|
||||
*/
|
||||
@interface JSBubbleMessageCell : UITableViewCell
|
||||
|
||||
/**
|
||||
* Returns the bubble view used in the cell. JSBubbleMessageCell adds the appropriate bubble view when you create the cell with a given cell type and bubbleImageView. This property is never `nil`.
|
||||
* @see JSBubbleView.
|
||||
* @see JSBubbleMessageType.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) JSBubbleView *bubbleView;
|
||||
|
||||
/**
|
||||
* Returns the label used to display the timestamp for the cell. This property may be `nil` if no timestamp is provided.
|
||||
* @see JSMessagesViewDataSource.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) UILabel *timestampLabel;
|
||||
|
||||
/**
|
||||
* Returns the image view used to display the avatar for the cell. This property may be `nil` if no avatar is provided.
|
||||
* @see JSMessagesViewDataSource.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) UIImageView *avatarImageView;
|
||||
|
||||
/**
|
||||
* Returns the label used to display the subtitle for the cell. This property may be `nil` if no subtitle is provided.
|
||||
* @see JSMessagesViewDataSource.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) UILabel *subtitleLabel;
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
/**
|
||||
* Initializes a message cell and returns it to the caller.
|
||||
*
|
||||
* @param type A constant indicating a message type. @see JSBubbleMessageType for details.
|
||||
* @param bubbleImageView An image view initialized with bubble images. The `UIImageView` properties `image` and `highlightedImage` must not be `nil`. @see JSBubbleImageViewFactory.
|
||||
* @param message An object that conforms to the `JSMessageData` protocol containing the message data for the cell.
|
||||
* @param displaysTimestamp A boolean value indicating whether or not the cell should display the date contained in message. Pass `YES` to display a timestamp, `NO` otherwise.
|
||||
* @param hasAvatar A boolean value indicating whether or not the cell should be initialized with an avatarImageView. Pass `YES` to initialize with an avatar, `NO` otherwise.
|
||||
* @param reuseIdentifier A string used to identify the cell object to be reused for drawing multiple rows of a JSMessagesViewController. This property must not be `nil`.
|
||||
*
|
||||
* @return An initialized `JSBubbleMessageCell` object or `nil` if the object could not be created.
|
||||
*/
|
||||
- (instancetype)initWithBubbleType:(JSBubbleMessageType)type
|
||||
bubbleImageView:(UIImageView *)bubbleImageView
|
||||
message:(id<JSMessageData>)message
|
||||
displaysTimestamp:(BOOL)displaysTimestamp
|
||||
hasAvatar:(BOOL)hasAvatar
|
||||
reuseIdentifier:(NSString *)reuseIdentifier;
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
/**
|
||||
* Sets the message object for the cell.
|
||||
*
|
||||
* @param message An object that conforms to the `JSMessageData` protocol containing the message data for the cell.
|
||||
*/
|
||||
- (void)setMessage:(id<JSMessageData>)message;
|
||||
|
||||
/**
|
||||
* Sets the imageView for the avatarImageView of the cell. The frame is set for you by `JSBubbleMessageCell`.
|
||||
*
|
||||
* @param imageView An imageView containing an avatar image. The `image` property of the `UIImageView` must not be `nil`.
|
||||
*/
|
||||
- (void)setAvatarImageView:(UIImageView *)imageView;
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
/**
|
||||
* Returns a contant indicating the message type for the cell.
|
||||
* @see JSBubbleMessageType.
|
||||
*
|
||||
* @return A contant indicating the message type.
|
||||
*/
|
||||
- (JSBubbleMessageType)messageType;
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
/**
|
||||
* Computes and returns the minimum necessary height of a `JSBubbleMessageCell` needed to display its contents.
|
||||
*
|
||||
* @param message An object that conforms to the `JSMessageData` protocol to display in the cell.
|
||||
* @param displaysAvatar A boolean value indicating whether or not the cell displays an avatar.
|
||||
* @param displaysTimestamp A boolean value indicating whether or not the cell displays a timestamp.
|
||||
*
|
||||
* @return The height required for the frame of the cell in order for the cell to display the entire contents of its subviews.
|
||||
*/
|
||||
+ (CGFloat)neededHeightForBubbleMessageCellWithMessage:(id<JSMessageData>)message
|
||||
displaysAvatar:(BOOL)displaysAvatar
|
||||
displaysTimestamp:(BOOL)displaysTimestamp;
|
||||
|
||||
@end
|
||||
372
JSMessagesViewController/Classes/JSBubbleMessageCell.m
Normal file
@ -0,0 +1,372 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSBubbleMessageCell.h"
|
||||
|
||||
#import "JSAvatarImageFactory.h"
|
||||
#import "UIColor+JSMessagesView.h"
|
||||
|
||||
static const CGFloat kJSLabelPadding = 5.0f;
|
||||
static const CGFloat kJSTimeStampLabelHeight = 15.0f;
|
||||
static const CGFloat kJSSubtitleLabelHeight = 15.0f;
|
||||
|
||||
|
||||
@interface JSBubbleMessageCell()
|
||||
|
||||
- (void)setup;
|
||||
- (void)configureTimestampLabel;
|
||||
- (void)configureAvatarImageView:(UIImageView *)imageView forMessageType:(JSBubbleMessageType)type;
|
||||
- (void)configureSubtitleLabelForMessageType:(JSBubbleMessageType)type;
|
||||
|
||||
- (void)configureWithType:(JSBubbleMessageType)type
|
||||
bubbleImageView:(UIImageView *)bubbleImageView
|
||||
message:(id<JSMessageData>)message
|
||||
displaysTimestamp:(BOOL)displaysTimestamp
|
||||
avatar:(BOOL)hasAvatar;
|
||||
|
||||
- (void)setText:(NSString *)text;
|
||||
- (void)setTimestamp:(NSDate *)date;
|
||||
- (void)setSubtitle:(NSString *)subtitle;
|
||||
|
||||
- (void)handleLongPressGesture:(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(handleLongPressGesture:)];
|
||||
[recognizer setMinimumPressDuration:0.4f];
|
||||
[self addGestureRecognizer:recognizer];
|
||||
}
|
||||
|
||||
- (void)configureTimestampLabel
|
||||
{
|
||||
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(kJSLabelPadding,
|
||||
kJSLabelPadding,
|
||||
self.contentView.frame.size.width - (kJSLabelPadding * 2.0f),
|
||||
kJSTimeStampLabelHeight)];
|
||||
label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
label.backgroundColor = [UIColor clearColor];
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.textColor = [UIColor js_messagesTimestampColorClassic];
|
||||
label.shadowColor = [UIColor whiteColor];
|
||||
label.shadowOffset = CGSizeMake(0.0f, 1.0f);
|
||||
label.font = [UIFont boldSystemFontOfSize:12.0f];
|
||||
|
||||
[self.contentView addSubview:label];
|
||||
[self.contentView bringSubviewToFront:label];
|
||||
_timestampLabel = label;
|
||||
}
|
||||
|
||||
- (void)configureAvatarImageView:(UIImageView *)imageView forMessageType:(JSBubbleMessageType)type
|
||||
{
|
||||
CGFloat avatarX = 0.5f;
|
||||
if (type == JSBubbleMessageTypeOutgoing) {
|
||||
avatarX = (self.contentView.frame.size.width - kJSAvatarImageSize);
|
||||
}
|
||||
|
||||
CGFloat avatarY = self.contentView.frame.size.height - kJSAvatarImageSize;
|
||||
if (_subtitleLabel) {
|
||||
avatarY -= kJSSubtitleLabelHeight;
|
||||
}
|
||||
|
||||
imageView.frame = CGRectMake(avatarX, avatarY, kJSAvatarImageSize, kJSAvatarImageSize);
|
||||
imageView.autoresizingMask = (UIViewAutoresizingFlexibleTopMargin
|
||||
| UIViewAutoresizingFlexibleLeftMargin
|
||||
| UIViewAutoresizingFlexibleRightMargin);
|
||||
|
||||
[self.contentView addSubview:imageView];
|
||||
_avatarImageView = imageView;
|
||||
}
|
||||
|
||||
- (void)configureSubtitleLabelForMessageType:(JSBubbleMessageType)type
|
||||
{
|
||||
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
label.backgroundColor = [UIColor clearColor];
|
||||
label.textAlignment = (type == JSBubbleMessageTypeOutgoing) ? NSTextAlignmentRight : NSTextAlignmentLeft;
|
||||
label.textColor = [UIColor js_messagesTimestampColorClassic];
|
||||
label.font = [UIFont systemFontOfSize:12.5f];
|
||||
|
||||
[self.contentView addSubview:label];
|
||||
_subtitleLabel = label;
|
||||
}
|
||||
|
||||
- (void)configureWithType:(JSBubbleMessageType)type
|
||||
bubbleImageView:(UIImageView *)bubbleImageView
|
||||
message:(id<JSMessageData>)message
|
||||
displaysTimestamp:(BOOL)displaysTimestamp
|
||||
avatar:(BOOL)hasAvatar
|
||||
{
|
||||
CGFloat bubbleY = 0.0f;
|
||||
CGFloat bubbleX = 0.0f;
|
||||
|
||||
CGFloat offsetX = 0.0f;
|
||||
|
||||
if (displaysTimestamp) {
|
||||
[self configureTimestampLabel];
|
||||
bubbleY = 14.0f;
|
||||
}
|
||||
|
||||
if ([message sender]) {
|
||||
[self configureSubtitleLabelForMessageType:type];
|
||||
}
|
||||
|
||||
if (hasAvatar) {
|
||||
offsetX = 4.0f;
|
||||
bubbleX = kJSAvatarImageSize;
|
||||
if (type == JSBubbleMessageTypeOutgoing) {
|
||||
offsetX = kJSAvatarImageSize - 4.0f;
|
||||
}
|
||||
|
||||
[self configureAvatarImageView:[[UIImageView alloc] init] forMessageType:type];
|
||||
}
|
||||
|
||||
CGRect frame = CGRectMake(bubbleX - offsetX,
|
||||
bubbleY,
|
||||
self.contentView.frame.size.width - bubbleX,
|
||||
self.contentView.frame.size.height - _timestampLabel.frame.size.height - _subtitleLabel.frame.size.height);
|
||||
|
||||
JSBubbleView *bubbleView = [[JSBubbleView alloc] initWithFrame:frame
|
||||
bubbleType:type
|
||||
bubbleImageView:bubbleImageView];
|
||||
|
||||
bubbleView.autoresizingMask = (UIViewAutoresizingFlexibleWidth
|
||||
| UIViewAutoresizingFlexibleHeight
|
||||
| UIViewAutoresizingFlexibleBottomMargin);
|
||||
|
||||
[self.contentView addSubview:bubbleView];
|
||||
[self.contentView sendSubviewToBack:bubbleView];
|
||||
_bubbleView = bubbleView;
|
||||
}
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
|
||||
{
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self setup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBubbleType:(JSBubbleMessageType)type
|
||||
bubbleImageView:(UIImageView *)bubbleImageView
|
||||
message:(id<JSMessageData>)message
|
||||
displaysTimestamp:(BOOL)displaysTimestamp
|
||||
hasAvatar:(BOOL)hasAvatar
|
||||
reuseIdentifier:(NSString *)reuseIdentifier
|
||||
{
|
||||
self = [self initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self configureWithType:type
|
||||
bubbleImageView:bubbleImageView
|
||||
message:message
|
||||
displaysTimestamp:displaysTimestamp
|
||||
avatar:hasAvatar];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_bubbleView = nil;
|
||||
_timestampLabel = nil;
|
||||
_avatarImageView = nil;
|
||||
_subtitleLabel = nil;
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma mark - TableViewCell
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
self.bubbleView.textView.text = nil;
|
||||
self.timestampLabel.text = nil;
|
||||
self.avatarImageView = nil;
|
||||
self.subtitleLabel.text = nil;
|
||||
}
|
||||
|
||||
- (void)setBackgroundColor:(UIColor *)color
|
||||
{
|
||||
[super setBackgroundColor:color];
|
||||
[self.contentView setBackgroundColor:color];
|
||||
[self.bubbleView setBackgroundColor:color];
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
self.bubbleView.textView.text = text;
|
||||
}
|
||||
|
||||
- (void)setTimestamp:(NSDate *)date
|
||||
{
|
||||
self.timestampLabel.text = [NSDateFormatter localizedStringFromDate:date
|
||||
dateStyle:NSDateFormatterMediumStyle
|
||||
timeStyle:NSDateFormatterShortStyle];
|
||||
}
|
||||
|
||||
- (void)setSubtitle:(NSString *)subtitle
|
||||
{
|
||||
self.subtitleLabel.text = subtitle;
|
||||
}
|
||||
|
||||
- (void)setMessage:(id<JSMessageData>)message
|
||||
{
|
||||
[self setText:[message text]];
|
||||
[self setTimestamp:[message date]];
|
||||
[self setSubtitle:[message sender]];
|
||||
}
|
||||
|
||||
- (void)setAvatarImageView:(UIImageView *)imageView
|
||||
{
|
||||
[_avatarImageView removeFromSuperview];
|
||||
_avatarImageView = nil;
|
||||
|
||||
[self configureAvatarImageView:imageView forMessageType:[self messageType]];
|
||||
}
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
- (JSBubbleMessageType)messageType
|
||||
{
|
||||
return _bubbleView.type;
|
||||
}
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
+ (CGFloat)neededHeightForBubbleMessageCellWithMessage:(id<JSMessageData>)message
|
||||
displaysAvatar:(BOOL)displaysAvatar
|
||||
displaysTimestamp:(BOOL)displaysTimestamp
|
||||
{
|
||||
CGFloat timestampHeight = displaysTimestamp ? kJSTimeStampLabelHeight : 0.0f;
|
||||
CGFloat avatarHeight = displaysAvatar ? kJSAvatarImageSize : 0.0f;
|
||||
CGFloat subtitleHeight = [message sender] ? kJSSubtitleLabelHeight : 0.0f;
|
||||
|
||||
CGFloat subviewHeights = timestampHeight + subtitleHeight + kJSLabelPadding;
|
||||
|
||||
CGFloat bubbleHeight = [JSBubbleView neededHeightForText:[message text]];
|
||||
|
||||
return subviewHeights + MAX(avatarHeight, bubbleHeight);
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
if (self.subtitleLabel) {
|
||||
self.subtitleLabel.frame = CGRectMake(kJSLabelPadding,
|
||||
self.contentView.frame.size.height - kJSSubtitleLabelHeight,
|
||||
self.contentView.frame.size.width - (kJSLabelPadding * 2.0f),
|
||||
kJSSubtitleLabelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Copying
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
return [super becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
||||
{
|
||||
return (action == @selector(copy:));
|
||||
}
|
||||
|
||||
- (void)copy:(id)sender
|
||||
{
|
||||
[[UIPasteboard generalPasteboard] setString:self.bubbleView.textView.text];
|
||||
[self resignFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - Gestures
|
||||
|
||||
- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)longPress
|
||||
{
|
||||
if (longPress.state != UIGestureRecognizerStateBegan || ![self becomeFirstResponder])
|
||||
return;
|
||||
|
||||
UIMenuController *menu = [UIMenuController sharedMenuController];
|
||||
CGRect targetRect = [self convertRect:[self.bubbleView bubbleFrame]
|
||||
fromView:self.bubbleView];
|
||||
|
||||
[menu setTargetRect:CGRectInset(targetRect, 0.0f, 4.0f) inView:self];
|
||||
|
||||
self.bubbleView.bubbleImageView.highlighted = YES;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleMenuWillShowNotification:)
|
||||
name:UIMenuControllerWillShowMenuNotification
|
||||
object:nil];
|
||||
[menu setMenuVisible:YES animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Notifications
|
||||
|
||||
- (void)handleMenuWillHideNotification:(NSNotification *)notification
|
||||
{
|
||||
self.bubbleView.bubbleImageView.highlighted = NO;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIMenuControllerWillHideMenuNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)handleMenuWillShowNotification:(NSNotification *)notification
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIMenuControllerWillShowMenuNotification
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleMenuWillHideNotification:)
|
||||
name:UIMenuControllerWillHideMenuNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
85
JSMessagesViewController/Classes/JSBubbleView.h
Normal file
@ -0,0 +1,85 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JSBubbleImageViewFactory.h"
|
||||
|
||||
/**
|
||||
* An instance of JSBubbleView is a means for displaying text in a speech bubble image to be placed in a JSBubbleMessageCell.
|
||||
* @see JSBubbleMessageCell.
|
||||
*/
|
||||
@interface JSBubbleView : UIView
|
||||
|
||||
/**
|
||||
* Returns the message type for this bubble view.
|
||||
* @see JSBubbleMessageType for descriptions of the constants used to specify bubble message type.
|
||||
*/
|
||||
@property (assign, nonatomic, readonly) JSBubbleMessageType type;
|
||||
|
||||
/**
|
||||
* Returns the image view containing the bubble image for this bubble view.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) UIImageView *bubbleImageView;
|
||||
|
||||
/**
|
||||
* Returns the text view containing the message text for this bubble view.
|
||||
*
|
||||
* @warning You may customize the propeties of textView, however you *must not* change its `font` property directly. Please use the `JSBubbleView` font property instead.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) UITextView *textView;
|
||||
|
||||
/**
|
||||
* The font for the text contained in the bubble view. The default value is `[UIFont systemFontOfSize:16.0f]`.
|
||||
*
|
||||
* @warning You must set this propety via `UIAppearance` only. *DO NOT set this property directly*.
|
||||
* @bug Setting this property directly, rather than via `UIAppearance` will cause the message bubbles and text to be laid out incorrectly.
|
||||
*/
|
||||
@property (strong, nonatomic) UIFont *font UI_APPEARANCE_SELECTOR;
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
/**
|
||||
* Initializes and returns a bubble view object having the given frame, bubble type, and bubble image view.
|
||||
*
|
||||
* @param frame A rectangle specifying the initial location and size of the bubble view in its superview's coordinates.
|
||||
* @param bubleType A constant that specifies the type of the bubble view. @see JSBubbleMessageType.
|
||||
* @param bubbleImageView An image view initialized with an image and highlighted image for this bubble view. @see JSBubbleImageViewFactory.
|
||||
*
|
||||
* @return An initialized `JSBubbleView` object or `nil` if the object could not be successfully initialized.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
bubbleType:(JSBubbleMessageType)bubleType
|
||||
bubbleImageView:(UIImageView *)bubbleImageView;
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
/**
|
||||
* The bubble view's frame rectangle is computed and set based on the size of the text that it needs to display.
|
||||
*
|
||||
* @return The frame of the bubble view.
|
||||
*/
|
||||
- (CGRect)bubbleFrame;
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
/**
|
||||
* Computes and returns the minimum necessary height of a `JSBubbleView` needed to display the given text.
|
||||
*
|
||||
* @param text The text to display in the bubble view.
|
||||
*
|
||||
* @return The height required for the frame of the bubble view in order to display the given text.
|
||||
*/
|
||||
+ (CGFloat)neededHeightForText:(NSString *)text;
|
||||
|
||||
@end
|
||||
250
JSMessagesViewController/Classes/JSBubbleView.m
Normal file
@ -0,0 +1,250 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSBubbleView.h"
|
||||
|
||||
#import "JSMessageInputView.h"
|
||||
#import "JSAvatarImageFactory.h"
|
||||
#import "NSString+JSMessagesView.h"
|
||||
|
||||
#define kMarginTop 8.0f
|
||||
#define kMarginBottom 4.0f
|
||||
#define kPaddingTop 4.0f
|
||||
#define kPaddingBottom 8.0f
|
||||
#define kBubblePaddingRight 35.0f
|
||||
|
||||
|
||||
@interface JSBubbleView()
|
||||
|
||||
- (void)setup;
|
||||
|
||||
- (void)addTextViewObservers;
|
||||
- (void)removeTextViewObservers;
|
||||
|
||||
+ (CGSize)textSizeForText:(NSString *)txt;
|
||||
+ (CGSize)neededSizeForText:(NSString *)text;
|
||||
+ (CGFloat)neededHeightForText:(NSString *)text;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation JSBubbleView
|
||||
|
||||
@synthesize font = _font;
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
- (void)setup
|
||||
{
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
}
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
bubbleType:(JSBubbleMessageType)bubleType
|
||||
bubbleImageView:(UIImageView *)bubbleImageView
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setup];
|
||||
|
||||
_type = bubleType;
|
||||
|
||||
bubbleImageView.userInteractionEnabled = YES;
|
||||
[self addSubview:bubbleImageView];
|
||||
_bubbleImageView = bubbleImageView;
|
||||
|
||||
UITextView *textView = [[UITextView alloc] init];
|
||||
textView.font = [UIFont systemFontOfSize:16.0f];
|
||||
textView.textColor = [UIColor blackColor];
|
||||
textView.editable = NO;
|
||||
textView.userInteractionEnabled = YES;
|
||||
textView.showsHorizontalScrollIndicator = NO;
|
||||
textView.showsVerticalScrollIndicator = NO;
|
||||
textView.scrollEnabled = NO;
|
||||
textView.backgroundColor = [UIColor clearColor];
|
||||
textView.contentInset = UIEdgeInsetsZero;
|
||||
textView.scrollIndicatorInsets = UIEdgeInsetsZero;
|
||||
textView.contentOffset = CGPointZero;
|
||||
[self addSubview:textView];
|
||||
[self bringSubviewToFront:textView];
|
||||
_textView = textView;
|
||||
|
||||
if ([_textView respondsToSelector:@selector(textContainerInset)]) {
|
||||
_textView.textContainerInset = UIEdgeInsetsMake(8.0f, 4.0f, 2.0f, 4.0f);
|
||||
}
|
||||
|
||||
[self addTextViewObservers];
|
||||
|
||||
// NOTE: TODO: textView frame & text inset
|
||||
// --------------------
|
||||
// future implementation for textView frame
|
||||
// in layoutSubviews : "self.textView.frame = textFrame;" is not needed
|
||||
// when setting the property : "_textView.textContainerInset = UIEdgeInsetsZero;"
|
||||
// unfortunately, this API is available in iOS 7.0+
|
||||
// update after dropping support for iOS 6.0
|
||||
// --------------------
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self removeTextViewObservers];
|
||||
_bubbleImageView = nil;
|
||||
_textView = nil;
|
||||
}
|
||||
|
||||
#pragma mark - KVO
|
||||
|
||||
- (void)addTextViewObservers
|
||||
{
|
||||
[_textView addObserver:self
|
||||
forKeyPath:@"text"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:nil];
|
||||
|
||||
[_textView addObserver:self
|
||||
forKeyPath:@"font"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:nil];
|
||||
|
||||
[_textView addObserver:self
|
||||
forKeyPath:@"textColor"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:nil];
|
||||
}
|
||||
|
||||
- (void)removeTextViewObservers
|
||||
{
|
||||
[_textView removeObserver:self forKeyPath:@"text"];
|
||||
[_textView removeObserver:self forKeyPath:@"font"];
|
||||
[_textView removeObserver:self forKeyPath:@"textColor"];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
if (object == self.textView) {
|
||||
if ([keyPath isEqualToString:@"text"]
|
||||
|| [keyPath isEqualToString:@"font"]
|
||||
|| [keyPath isEqualToString:@"textColor"]) {
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setFont:(UIFont *)font
|
||||
{
|
||||
_font = font;
|
||||
_textView.font = font;
|
||||
}
|
||||
|
||||
#pragma mark - UIAppearance Getters
|
||||
|
||||
- (UIFont *)font
|
||||
{
|
||||
if (_font == nil) {
|
||||
_font = [[[self class] appearance] font];
|
||||
}
|
||||
|
||||
if (_font != nil) {
|
||||
return _font;
|
||||
}
|
||||
|
||||
return [UIFont systemFontOfSize:16.0f];
|
||||
}
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
- (CGRect)bubbleFrame
|
||||
{
|
||||
CGSize bubbleSize = [JSBubbleView neededSizeForText:self.textView.text];
|
||||
|
||||
return CGRectIntegral(CGRectMake((self.type == JSBubbleMessageTypeOutgoing ? self.frame.size.width - bubbleSize.width : 0.0f),
|
||||
kMarginTop,
|
||||
bubbleSize.width,
|
||||
bubbleSize.height + kMarginBottom));
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
self.bubbleImageView.frame = [self bubbleFrame];
|
||||
|
||||
CGFloat textX = self.bubbleImageView.frame.origin.x;
|
||||
|
||||
if (self.type == JSBubbleMessageTypeIncoming) {
|
||||
textX += (self.bubbleImageView.image.capInsets.left / 2.0f);
|
||||
}
|
||||
|
||||
CGRect textFrame = CGRectMake(textX,
|
||||
self.bubbleImageView.frame.origin.y,
|
||||
self.bubbleImageView.frame.size.width - (self.bubbleImageView.image.capInsets.right / 2.0f),
|
||||
self.bubbleImageView.frame.size.height - kMarginTop);
|
||||
|
||||
self.textView.frame = CGRectIntegral(textFrame);
|
||||
}
|
||||
|
||||
#pragma mark - Bubble view
|
||||
|
||||
+ (CGSize)textSizeForText:(NSString *)txt
|
||||
{
|
||||
CGFloat maxWidth = [UIScreen mainScreen].applicationFrame.size.width * 0.70f;
|
||||
CGFloat maxHeight = MAX([JSMessageTextView numberOfLinesForMessage:txt],
|
||||
[txt js_numberOfLines]) * [JSMessageInputView textViewLineHeight];
|
||||
maxHeight += kJSAvatarImageSize;
|
||||
|
||||
CGSize stringSize;
|
||||
|
||||
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_0) {
|
||||
CGRect stringRect = [txt boundingRectWithSize:CGSizeMake(maxWidth, maxHeight)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin
|
||||
attributes:@{ NSFontAttributeName : [[JSBubbleView appearance] font] }
|
||||
context:nil];
|
||||
|
||||
stringSize = CGRectIntegral(stringRect).size;
|
||||
}
|
||||
else {
|
||||
stringSize = [txt sizeWithFont:[[JSBubbleView appearance] font]
|
||||
constrainedToSize:CGSizeMake(maxWidth, maxHeight)];
|
||||
}
|
||||
|
||||
return CGSizeMake(roundf(stringSize.width), roundf(stringSize.height));
|
||||
}
|
||||
|
||||
+ (CGSize)neededSizeForText:(NSString *)text
|
||||
{
|
||||
CGSize textSize = [JSBubbleView textSizeForText:text];
|
||||
|
||||
return CGSizeMake(textSize.width + kBubblePaddingRight,
|
||||
textSize.height + kPaddingTop + kPaddingBottom);
|
||||
}
|
||||
|
||||
+ (CGFloat)neededHeightForText:(NSString *)text
|
||||
{
|
||||
CGSize size = [JSBubbleView neededSizeForText:text];
|
||||
return size.height + kMarginTop + kMarginBottom;
|
||||
}
|
||||
|
||||
@end
|
||||
68
JSMessagesViewController/Classes/JSDismissiveTextView.h
Normal file
@ -0,0 +1,68 @@
|
||||
//
|
||||
// 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 <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
* The delegate of a `JSDismissiveTextView` object must adopt the `JSDismissiveTextViewDelegate` protocol.
|
||||
*/
|
||||
@protocol JSDismissiveTextViewDelegate <NSObject>
|
||||
|
||||
@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<JSDismissiveTextViewDelegate> keyboardDelegate;
|
||||
|
||||
/**
|
||||
* The pan gesture recognizer for the text view.
|
||||
*/
|
||||
@property (strong, nonatomic) UIPanGestureRecognizer *dismissivePanGestureRecognizer;
|
||||
|
||||
@end
|
||||
193
JSMessagesViewController/Classes/JSDismissiveTextView.m
Normal file
@ -0,0 +1,193 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
BOOL isPhone = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone;
|
||||
BOOL keyBoardWasSplitOnPad = [[notification userInfo][@"UIKeyboardFrameChangedByUserInteraction"] boolValue];
|
||||
|
||||
if (isPhone || keyBoardWasSplitOnPad) {
|
||||
[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:nil];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// gesture is currently panning, match keyboard y to touch y
|
||||
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
|
||||
53
JSMessagesViewController/Classes/JSMessage.h
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "JSMessageData.h"
|
||||
|
||||
/**
|
||||
* A `JSMessage` object represents a single user message. This is a concrete class that implements the `JSMessageData` protocol. It contains the message text, its sender, and the date that the message was sent.
|
||||
*/
|
||||
@interface JSMessage : NSObject <JSMessageData, NSCoding, NSCopying>
|
||||
|
||||
/**
|
||||
* The body text of the message. The default value is the empty string `@" "`. This value must not be `nil`.
|
||||
*/
|
||||
@property (copy, nonatomic) NSString *text;
|
||||
|
||||
/**
|
||||
* The name of user who sent the message. The default value is `nil`.
|
||||
*/
|
||||
@property (copy, nonatomic) NSString *sender;
|
||||
|
||||
/**
|
||||
* The date that the message was sent. The default value is `nil`.
|
||||
*/
|
||||
@property (strong, nonatomic) NSDate *date;
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
/**
|
||||
* Initializes and returns a message object having the given text, sender, and date.
|
||||
*
|
||||
* @param text The body text of the message.
|
||||
* @param sender The name of the user who sent the message.
|
||||
* @param date The date that the message was sent.
|
||||
*
|
||||
* @return An initialized `JSMessage` object or `nil` if the object could not be successfully initialized.
|
||||
*/
|
||||
- (instancetype)initWithText:(NSString *)text
|
||||
sender:(NSString *)sender
|
||||
date:(NSDate *)date;
|
||||
|
||||
@end
|
||||
70
JSMessagesViewController/Classes/JSMessage.m
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSMessage.h"
|
||||
|
||||
@implementation JSMessage
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithText:(NSString *)text
|
||||
sender:(NSString *)sender
|
||||
date:(NSDate *)date
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_text = text ? text : @" ";
|
||||
_sender = sender;
|
||||
_date = date;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_text = nil;
|
||||
_sender = nil;
|
||||
_date = nil;
|
||||
}
|
||||
|
||||
#pragma mark - NSCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_text = [aDecoder decodeObjectForKey:@"text"];
|
||||
_sender = [aDecoder decodeObjectForKey:@"sender"];
|
||||
_date = [aDecoder decodeObjectForKey:@"date"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:self.text forKey:@"text"];
|
||||
[aCoder encodeObject:self.sender forKey:@"sender"];
|
||||
[aCoder encodeObject:self.date forKey:@"date"];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
return [[[self class] allocWithZone:zone] initWithText:[self.text copy]
|
||||
sender:[self.sender copy]
|
||||
date:[self.date copy]];
|
||||
}
|
||||
|
||||
@end
|
||||
43
JSMessagesViewController/Classes/JSMessageData.h
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* The `JSMessageData` protocol defines the common interface through which `JSMessagesViewController` interacts with message model objects.
|
||||
* It declares the methods that a class must implement so that instances of that class can be displayed properly by a `JSMessagesViewController`.
|
||||
*/
|
||||
@protocol JSMessageData <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
* @return The body text of the message.
|
||||
* @warning This value must not be `nil`.
|
||||
*/
|
||||
- (NSString *)text;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* @return The name of the user who sent the message.
|
||||
*/
|
||||
- (NSString *)sender;
|
||||
|
||||
/**
|
||||
* @return The date that the message was sent.
|
||||
*/
|
||||
- (NSDate *)date;
|
||||
|
||||
@end
|
||||
97
JSMessagesViewController/Classes/JSMessageInputView.h
Normal file
@ -0,0 +1,97 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JSMessageTextView.h"
|
||||
|
||||
/**
|
||||
* The appearance style of the input bar view for composing a new message.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, JSMessageInputViewStyle) {
|
||||
/**
|
||||
* An input view style that has the appearance as seen in iOS 6 and before.
|
||||
*/
|
||||
JSMessageInputViewStyleClassic,
|
||||
/**
|
||||
* An input view style that has the appearance as seen in iOS 7 and later.
|
||||
*/
|
||||
JSMessageInputViewStyleFlat
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An instance of `JSMessageInputView` defines the input toolbar for composing a new message that is to be displayed above the keyboard.
|
||||
*/
|
||||
@interface JSMessageInputView : UIImageView
|
||||
|
||||
/**
|
||||
* Returns the style appearance for the input view.
|
||||
* @see JSMessageInputViewStyle.
|
||||
*/
|
||||
@property (assign, nonatomic, readonly) JSMessageInputViewStyle style;
|
||||
|
||||
/**
|
||||
* Returns the textView into which a new message is composed. This property is never `nil`.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) JSMessageTextView *textView;
|
||||
|
||||
/**
|
||||
* The send button for the input view. The default value is an initialized `UIButton` whose appearance is styled according to the value of style during initialization.
|
||||
* @see JSMessageInputViewStyle.
|
||||
*/
|
||||
@property (weak, nonatomic) UIButton *sendButton;
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
/**
|
||||
* Initializes and returns an input view having the given frame, style, delegate, and panGestureRecognizer.
|
||||
*
|
||||
* @param frame A rectangle specifying the initial location and size of the bubble view in its superview's coordinates.
|
||||
* @param style The style of the input view. @see JSMessageInputViewStyle.
|
||||
* @param delegate An object that conforms to the `UITextViewDelegate` protocol and `JSDismissiveTextViewDelegate` protocol.
|
||||
* @see JSDismissiveTextViewDelegate.
|
||||
* @param panGestureRecognizer A `UIPanGestureRecognizer` used to dismiss the input view by dragging down.
|
||||
*
|
||||
* @return An initialized `JSMessageInputView` object or `nil` if the object could not be successfully initialized.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
style:(JSMessageInputViewStyle)style
|
||||
delegate:(id<UITextViewDelegate, JSDismissiveTextViewDelegate>)delegate
|
||||
panGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer;
|
||||
|
||||
#pragma mark - Message input view
|
||||
|
||||
/**
|
||||
* Adjusts the input view's frame height by the given value.
|
||||
*
|
||||
* @param changeInHeight The delta value by which to increase or decrease the existing height for the input view.
|
||||
*/
|
||||
- (void)adjustTextViewHeightBy:(CGFloat)changeInHeight;
|
||||
|
||||
/**
|
||||
* @return A constant indicating the height of one line of text in the input view.
|
||||
*/
|
||||
+ (CGFloat)textViewLineHeight;
|
||||
|
||||
/**
|
||||
* @return A contant indicating the maximum number of lines of text that can be displayed in the textView.
|
||||
*/
|
||||
+ (CGFloat)maxLines;
|
||||
|
||||
/**
|
||||
* @return The maximum height of the input view as determined by `maxLines` and `textViewLineHeight`. This value is used for controlling the animation of the growing and shrinking of the input view as the text changes in the textView.
|
||||
*/
|
||||
+ (CGFloat)maxHeight;
|
||||
|
||||
@end
|
||||
238
JSMessagesViewController/Classes/JSMessageInputView.m
Normal file
@ -0,0 +1,238 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSMessageInputView.h"
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "JSBubbleView.h"
|
||||
#import "NSString+JSMessagesView.h"
|
||||
#import "UIColor+JSMessagesView.h"
|
||||
|
||||
@interface JSMessageInputView ()
|
||||
|
||||
- (void)setup;
|
||||
- (void)configureInputBarWithStyle:(JSMessageInputViewStyle)style;
|
||||
- (void)configureSendButtonWithStyle:(JSMessageInputViewStyle)style;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation JSMessageInputView
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (void)setup
|
||||
{
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
self.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin);
|
||||
self.opaque = YES;
|
||||
self.userInteractionEnabled = YES;
|
||||
}
|
||||
|
||||
- (void)configureInputBarWithStyle:(JSMessageInputViewStyle)style
|
||||
{
|
||||
CGFloat sendButtonWidth = (style == JSMessageInputViewStyleClassic) ? 78.0f : 64.0f;
|
||||
|
||||
CGFloat width = self.frame.size.width - sendButtonWidth;
|
||||
CGFloat height = [JSMessageInputView textViewLineHeight];
|
||||
|
||||
JSMessageTextView *textView = [[JSMessageTextView alloc] initWithFrame:CGRectZero];
|
||||
[self addSubview:textView];
|
||||
_textView = textView;
|
||||
|
||||
if (style == JSMessageInputViewStyleClassic) {
|
||||
_textView.frame = CGRectMake(6.0f, 3.0f, width, height);
|
||||
_textView.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
self.image = [[UIImage imageNamed:@"input-bar-background"] resizableImageWithCapInsets:UIEdgeInsetsMake(19.0f, 3.0f, 19.0f, 3.0f)
|
||||
resizingMode:UIImageResizingModeStretch];
|
||||
|
||||
UIImageView *inputFieldBack = [[UIImageView alloc] initWithFrame:CGRectMake(_textView.frame.origin.x - 1.0f,
|
||||
0.0f,
|
||||
_textView.frame.size.width + 2.0f,
|
||||
self.frame.size.height)];
|
||||
inputFieldBack.image = [[UIImage imageNamed:@"input-field-cover"] resizableImageWithCapInsets:UIEdgeInsetsMake(20.0f, 12.0f, 18.0f, 18.0f)];
|
||||
inputFieldBack.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
|
||||
inputFieldBack.backgroundColor = [UIColor clearColor];
|
||||
[self addSubview:inputFieldBack];
|
||||
}
|
||||
else {
|
||||
_textView.frame = CGRectMake(4.0f, 4.5f, width, height);
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.layer.borderColor = [UIColor colorWithWhite:0.8f alpha:1.0f].CGColor;
|
||||
_textView.layer.borderWidth = 0.65f;
|
||||
_textView.layer.cornerRadius = 6.0f;
|
||||
|
||||
self.image = [[UIImage imageNamed:@"input-bar-flat"] resizableImageWithCapInsets:UIEdgeInsetsMake(2.0f, 0.0f, 0.0f, 0.0f)
|
||||
resizingMode:UIImageResizingModeStretch];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configureSendButtonWithStyle:(JSMessageInputViewStyle)style
|
||||
{
|
||||
UIButton *sendButton;
|
||||
|
||||
if (style == JSMessageInputViewStyleClassic) {
|
||||
sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
|
||||
UIEdgeInsets insets = UIEdgeInsetsMake(0.0f, 13.0f, 0.0f, 13.0f);
|
||||
UIImage *sendBack = [[UIImage imageNamed:@"send-button"] resizableImageWithCapInsets:insets];
|
||||
UIImage *sendBackHighLighted = [[UIImage imageNamed:@"send-button-pressed"] resizableImageWithCapInsets:insets];
|
||||
[sendButton setBackgroundImage:sendBack forState:UIControlStateNormal];
|
||||
[sendButton setBackgroundImage:sendBack forState:UIControlStateDisabled];
|
||||
[sendButton setBackgroundImage:sendBackHighLighted forState:UIControlStateHighlighted];
|
||||
|
||||
UIColor *titleShadow = [UIColor colorWithRed:0.325f green:0.463f blue:0.675f alpha:1.0f];
|
||||
[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];
|
||||
|
||||
sendButton.titleLabel.font = [UIFont boldSystemFontOfSize:16.0f];
|
||||
}
|
||||
else {
|
||||
sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
sendButton.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[sendButton setTitleColor:[UIColor js_bubbleBlueColor] forState:UIControlStateNormal];
|
||||
[sendButton setTitleColor:[UIColor js_bubbleBlueColor] forState:UIControlStateHighlighted];
|
||||
[sendButton setTitleColor:[UIColor js_bubbleLightGrayColor] forState:UIControlStateDisabled];
|
||||
|
||||
sendButton.titleLabel.font = [UIFont boldSystemFontOfSize:18.0f];
|
||||
}
|
||||
|
||||
NSString *title = NSLocalizedString(@"Send", nil);
|
||||
[sendButton setTitle:title forState:UIControlStateNormal];
|
||||
[sendButton setTitle:title forState:UIControlStateHighlighted];
|
||||
[sendButton setTitle:title forState:UIControlStateDisabled];
|
||||
|
||||
sendButton.autoresizingMask = (UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin);
|
||||
|
||||
[self setSendButton:sendButton];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
style:(JSMessageInputViewStyle)style
|
||||
delegate:(id<UITextViewDelegate, JSDismissiveTextViewDelegate>)delegate
|
||||
panGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_style = style;
|
||||
[self setup];
|
||||
[self configureInputBarWithStyle:style];
|
||||
[self configureSendButtonWithStyle:style];
|
||||
|
||||
_textView.delegate = delegate;
|
||||
_textView.keyboardDelegate = delegate;
|
||||
_textView.dismissivePanGestureRecognizer = panGestureRecognizer;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_textView = nil;
|
||||
_sendButton = nil;
|
||||
}
|
||||
|
||||
#pragma mark - UIView
|
||||
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
[self.textView resignFirstResponder];
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setSendButton:(UIButton *)btn
|
||||
{
|
||||
if (_sendButton)
|
||||
[_sendButton removeFromSuperview];
|
||||
|
||||
if (self.style == JSMessageInputViewStyleClassic) {
|
||||
btn.frame = CGRectMake(self.frame.size.width - 65.0f, 8.0f, 59.0f, 26.0f);
|
||||
}
|
||||
else {
|
||||
CGFloat padding = 8.0f;
|
||||
btn.frame = CGRectMake(self.textView.frame.origin.x + self.textView.frame.size.width,
|
||||
padding,
|
||||
60.0f,
|
||||
self.textView.frame.size.height - padding);
|
||||
}
|
||||
|
||||
[self addSubview:btn];
|
||||
_sendButton = btn;
|
||||
}
|
||||
|
||||
#pragma mark - Message input view
|
||||
|
||||
- (void)adjustTextViewHeightBy:(CGFloat)changeInHeight
|
||||
{
|
||||
CGRect prevFrame = self.textView.frame;
|
||||
|
||||
NSUInteger numLines = MAX([self.textView numberOfLinesOfText],
|
||||
[self.textView.text js_numberOfLines]);
|
||||
|
||||
// below iOS 7, if you set the text view frame programmatically, the KVO will continue notifying
|
||||
// to avoid that, we are removing the observer before setting the frame and add the observer after setting frame here.
|
||||
[self.textView removeObserver:_textView.keyboardDelegate
|
||||
forKeyPath:@"contentSize"];
|
||||
|
||||
self.textView.frame = CGRectMake(prevFrame.origin.x,
|
||||
prevFrame.origin.y,
|
||||
prevFrame.size.width,
|
||||
prevFrame.size.height + changeInHeight);
|
||||
|
||||
[self.textView addObserver:_textView.keyboardDelegate
|
||||
forKeyPath:@"contentSize"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:nil];
|
||||
|
||||
self.textView.contentInset = UIEdgeInsetsMake((numLines >= 6 ? 4.0f : 0.0f),
|
||||
0.0f,
|
||||
(numLines >= 6 ? 4.0f : 0.0f),
|
||||
0.0f);
|
||||
|
||||
// from iOS 7, the content size will be accurate only if the scrolling is enabled.
|
||||
self.textView.scrollEnabled = YES;
|
||||
|
||||
if (numLines >= 6) {
|
||||
CGPoint bottomOffset = CGPointMake(0.0f, self.textView.contentSize.height - self.textView.bounds.size.height);
|
||||
[self.textView setContentOffset:bottomOffset animated:YES];
|
||||
[self.textView scrollRangeToVisible:NSMakeRange(self.textView.text.length - 2, 1)];
|
||||
}
|
||||
}
|
||||
|
||||
+ (CGFloat)textViewLineHeight
|
||||
{
|
||||
return 36.0f; // for fontSize 16.0f
|
||||
}
|
||||
|
||||
+ (CGFloat)maxLines
|
||||
{
|
||||
return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) ? 4.0f : 8.0f;
|
||||
}
|
||||
|
||||
+ (CGFloat)maxHeight
|
||||
{
|
||||
return ([JSMessageInputView maxLines] + 1.0f) * [JSMessageInputView textViewLineHeight];
|
||||
}
|
||||
|
||||
@end
|
||||
42
JSMessagesViewController/Classes/JSMessageSoundEffect.h
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* `JSMessageSoundEffect` is a class that encapsulates the playing of the default sound effects for sending and receiving messages in `JSMessagesViewController`. It is backed by an instance of `JSQSystemSoundPlayer`.
|
||||
*/
|
||||
@interface JSMessageSoundEffect : NSObject
|
||||
|
||||
/**
|
||||
* Plays the default sound for received messages.
|
||||
*/
|
||||
+ (void)playMessageReceivedSound;
|
||||
|
||||
/**
|
||||
* Plays the default sound for received messages *as an alert*, invoking device vibration if available.
|
||||
*/
|
||||
+ (void)playMessageReceivedAlert;
|
||||
|
||||
/**
|
||||
* Plays the default sound for sent messages.
|
||||
*/
|
||||
+ (void)playMessageSentSound;
|
||||
|
||||
/**
|
||||
* Plays the default sound for sent messages *as an alert*, invoking device vibration if available.
|
||||
*/
|
||||
+ (void)playMessageSentAlert;
|
||||
|
||||
@end
|
||||
47
JSMessagesViewController/Classes/JSMessageSoundEffect.m
Normal file
@ -0,0 +1,47 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSMessageSoundEffect.h"
|
||||
#import <JSQSystemSoundPlayer/JSQSystemSoundPlayer.h>
|
||||
|
||||
static NSString * const kJSMessageReceived = @"message-received";
|
||||
static NSString * const kJSMessageSent = @"message-sent";
|
||||
|
||||
@implementation JSMessageSoundEffect
|
||||
|
||||
+ (void)playMessageReceivedSound
|
||||
{
|
||||
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithName:kJSMessageReceived
|
||||
extension:kJSQSystemSoundTypeAIFF];
|
||||
}
|
||||
|
||||
+ (void)playMessageReceivedAlert
|
||||
{
|
||||
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithName:kJSMessageReceived
|
||||
extension:kJSQSystemSoundTypeAIFF];
|
||||
}
|
||||
|
||||
+ (void)playMessageSentSound
|
||||
{
|
||||
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithName:kJSMessageSent
|
||||
extension:kJSQSystemSoundTypeAIFF];
|
||||
}
|
||||
|
||||
+ (void)playMessageSentAlert
|
||||
{
|
||||
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithName:kJSMessageSent
|
||||
extension:kJSQSystemSoundTypeAIFF];
|
||||
}
|
||||
|
||||
@end
|
||||
22
JSMessagesViewController/Classes/JSMessageTableView.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
* An instance of `JSMessageTableView` is a subclass of `UITableView` and is means for displaying a list of messages between a group of users.
|
||||
*/
|
||||
@interface JSMessageTableView : UITableView
|
||||
|
||||
@end
|
||||
19
JSMessagesViewController/Classes/JSMessageTableView.m
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSMessageTableView.h"
|
||||
|
||||
@implementation JSMessageTableView
|
||||
|
||||
@end
|
||||
55
JSMessagesViewController/Classes/JSMessageTextView.h
Normal file
@ -0,0 +1,55 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSDismissiveTextView.h"
|
||||
|
||||
/**
|
||||
* An instance of `JSMessageTextView` is a means for displaying an input text view above a keyboard as a subview of a keyboard's `inputAccessoryView`. It is used for composing messages and adds support for a placeholder like UITextField.
|
||||
*/
|
||||
@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
|
||||
@ -1,115 +1,83 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.jessesquires.com
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSQMessagesViewController
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// GitHub
|
||||
// https://github.com/jessesquires/JSQMessagesViewController
|
||||
//
|
||||
//
|
||||
// License
|
||||
// Copyright (c) 2014 Jesse Squires
|
||||
// Released under an MIT license: http://opensource.org/licenses/MIT
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSQMessagesComposerTextView.h"
|
||||
#import "JSMessageTextView.h"
|
||||
#import "JSBubbleView.h"
|
||||
#import "NSString+JSMessagesView.h"
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
@interface JSMessageTextView ()
|
||||
|
||||
#import "NSString+JSQMessages.h"
|
||||
- (void)setup;
|
||||
|
||||
|
||||
@interface JSQMessagesComposerTextView ()
|
||||
|
||||
- (void)jsq_configureTextView;
|
||||
|
||||
- (void)jsq_addTextViewNotificationObservers;
|
||||
- (void)jsq_removeTextViewNotificationObservers;
|
||||
- (void)jsq_didReceiveTextViewNotification:(NSNotification *)notification;
|
||||
|
||||
- (NSDictionary *)jsq_placeholderTextAttributes;
|
||||
- (void)addTextViewNotificationObservers;
|
||||
- (void)removeTextViewNotificationObservers;
|
||||
- (void)didReceiveTextViewNotification:(NSNotification *)notification;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation JSQMessagesComposerTextView
|
||||
@implementation JSMessageTextView
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (void)jsq_configureTextView
|
||||
- (void)setup
|
||||
{
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
CGFloat cornerRadius = 6.0f;
|
||||
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
//Signal: Comment out elements of Composer view
|
||||
// self.layer.borderWidth = 0.5f;
|
||||
// self.layer.borderColor = [UIColor lightGrayColor].CGColor;
|
||||
// self.layer.cornerRadius = cornerRadius;
|
||||
|
||||
self.scrollIndicatorInsets = UIEdgeInsetsMake(cornerRadius, 0.0f, cornerRadius, 0.0f);
|
||||
|
||||
self.textContainerInset = UIEdgeInsetsMake(4.0f, 2.0f, 4.0f, 2.0f);
|
||||
self.contentInset = UIEdgeInsetsMake(1.0f, 0.0f, 1.0f, 0.0f);
|
||||
_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 fontWithName:@"HelveticaNeue-Light" size:16.0f];
|
||||
// self.font = [UIFont systemFontOfSize:16.0f];
|
||||
self.font = [UIFont systemFontOfSize:16.0f];
|
||||
self.textColor = [UIColor blackColor];
|
||||
self.textAlignment = NSTextAlignmentNatural;
|
||||
|
||||
self.contentMode = UIViewContentModeRedraw;
|
||||
self.dataDetectorTypes = UIDataDetectorTypeNone;
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
self.keyboardAppearance = UIKeyboardAppearanceDefault;
|
||||
self.keyboardType = UIKeyboardTypeDefault;
|
||||
self.returnKeyType = UIReturnKeyDefault;
|
||||
self.textAlignment = NSTextAlignmentLeft;
|
||||
|
||||
self.text = nil;
|
||||
|
||||
_placeHolder = nil;
|
||||
_placeHolderTextColor = [UIColor lightGrayColor];
|
||||
|
||||
[self jsq_addTextViewNotificationObservers];
|
||||
[self addTextViewNotificationObservers];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame textContainer:textContainer];
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self jsq_configureTextView];
|
||||
[self setup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
[super awakeFromNib];
|
||||
[self jsq_configureTextView];
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self setup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self jsq_removeTextViewNotificationObservers];
|
||||
[self removeTextViewNotificationObservers];
|
||||
_placeHolder = nil;
|
||||
_placeHolderTextColor = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Composer text view
|
||||
|
||||
- (BOOL)hasText
|
||||
{
|
||||
return ([[self.text jsq_stringByTrimingWhitespace] length] > 0);
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setPlaceHolder:(NSString *)placeHolder
|
||||
@ -118,7 +86,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
_placeHolder = [placeHolder copy];
|
||||
NSUInteger maxChars = [JSMessageTextView maxCharactersPerLine];
|
||||
if ([placeHolder length] > maxChars) {
|
||||
placeHolder = [placeHolder substringToIndex:maxChars - 8];
|
||||
placeHolder = [[placeHolder js_stringByTrimingWhitespace] stringByAppendingFormat:@"..."];
|
||||
}
|
||||
|
||||
_placeHolder = placeHolder;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
@ -132,7 +106,24 @@
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
#pragma mark - UITextView overrides
|
||||
#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
|
||||
{
|
||||
@ -165,34 +156,53 @@
|
||||
[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:CGRectInset(rect, 7.0f, 5.0f)
|
||||
withAttributes:[self jsq_placeholderTextAttributes]];
|
||||
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_0) {
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
paragraphStyle.alignment = self.textAlignment;
|
||||
|
||||
[self.placeHolder drawInRect:placeHolderRect
|
||||
withAttributes:@{ NSFontAttributeName : self.font,
|
||||
NSForegroundColorAttributeName : self.placeHolderTextColor,
|
||||
NSParagraphStyleAttributeName : paragraphStyle }];
|
||||
}
|
||||
else {
|
||||
[self.placeHolder drawInRect:placeHolderRect
|
||||
withFont:self.font
|
||||
lineBreakMode:NSLineBreakByTruncatingTail
|
||||
alignment:self.textAlignment];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Notifications
|
||||
|
||||
- (void)jsq_addTextViewNotificationObservers
|
||||
- (void)addTextViewNotificationObservers
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(jsq_didReceiveTextViewNotification:)
|
||||
selector:@selector(didReceiveTextViewNotification:)
|
||||
name:UITextViewTextDidChangeNotification
|
||||
object:self];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(jsq_didReceiveTextViewNotification:)
|
||||
selector:@selector(didReceiveTextViewNotification:)
|
||||
name:UITextViewTextDidBeginEditingNotification
|
||||
object:self];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(jsq_didReceiveTextViewNotification:)
|
||||
selector:@selector(didReceiveTextViewNotification:)
|
||||
name:UITextViewTextDidEndEditingNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
- (void)jsq_removeTextViewNotificationObservers
|
||||
- (void)removeTextViewNotificationObservers
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UITextViewTextDidChangeNotification
|
||||
@ -207,22 +217,9 @@
|
||||
object:self];
|
||||
}
|
||||
|
||||
- (void)jsq_didReceiveTextViewNotification:(NSNotification *)notification
|
||||
- (void)didReceiveTextViewNotification:(NSNotification *)notification
|
||||
{
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
#pragma mark - Utilities
|
||||
|
||||
- (NSDictionary *)jsq_placeholderTextAttributes
|
||||
{
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
paragraphStyle.alignment = self.textAlignment;
|
||||
|
||||
return @{ NSFontAttributeName : self.font,
|
||||
NSForegroundColorAttributeName : self.placeHolderTextColor,
|
||||
NSParagraphStyleAttributeName : paragraphStyle };
|
||||
}
|
||||
|
||||
@end
|
||||
222
JSMessagesViewController/Classes/JSMessagesViewController.h
Normal file
@ -0,0 +1,222 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JSMessageTableView.h"
|
||||
#import "JSMessageData.h"
|
||||
#import "JSBubbleMessageCell.h"
|
||||
#import "JSMessageInputView.h"
|
||||
#import "JSAvatarImageFactory.h"
|
||||
#import "JSBubbleImageViewFactory.h"
|
||||
#import "JSMessageSoundEffect.h"
|
||||
#import "UIColor+JSMessagesView.h"
|
||||
|
||||
/**
|
||||
* The delegate of a `JSMessagesViewController` must adopt the `JSMessagesViewDelegate` protocol.
|
||||
*/
|
||||
@protocol JSMessagesViewDelegate <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
* Tells the delegate that the user has sent a message with the specified text, sender, and date.
|
||||
*
|
||||
* @param text The text that was present in the textView of the messageInputView when the send button was pressed.
|
||||
* @param sender The user who sent the message.
|
||||
* @param date The date and time at which the message was sent.
|
||||
*/
|
||||
- (void)didSendText:(NSString *)text fromSender:(NSString *)sender onDate:(NSDate *)date;
|
||||
|
||||
/**
|
||||
* Asks the delegate for the message type for the row at the specified index path.
|
||||
*
|
||||
* @param indexPath The index path of the row to be displayed.
|
||||
*
|
||||
* @return A constant describing the message type.
|
||||
* @see JSBubbleMessageType.
|
||||
*/
|
||||
- (JSBubbleMessageType)messageTypeForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Asks the delegate for the bubble image view for the row at the specified index path with the specified type.
|
||||
*
|
||||
* @param type The type of message for the row located at indexPath.
|
||||
* @param indexPath The index path of the row to be displayed.
|
||||
*
|
||||
* @return A `UIImageView` with both `image` and `highlightedImage` properties set.
|
||||
* @see JSBubbleImageViewFactory.
|
||||
*/
|
||||
- (UIImageView *)bubbleImageViewWithType:(JSBubbleMessageType)type
|
||||
forRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Asks the delegate for the input view style.
|
||||
*
|
||||
* @return A constant describing the input view style.
|
||||
* @see JSMessageInputViewStyle.
|
||||
*/
|
||||
- (JSMessageInputViewStyle)inputViewStyle;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Asks the delegate if a timestamp should be displayed *above* the row at the specified index path.
|
||||
*
|
||||
* @param indexPath The index path of the row to be displayed.
|
||||
*
|
||||
* @return A boolean value specifying whether or not a timestamp should be displayed for the row at indexPath. The default value is `YES`.
|
||||
*/
|
||||
- (BOOL)shouldDisplayTimestampForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Asks the delegate to configure or further customize the given cell at the specified index path.
|
||||
*
|
||||
* @param cell The message cell to configure.
|
||||
* @param indexPath The index path for cell.
|
||||
*/
|
||||
- (void)configureCell:(JSBubbleMessageCell *)cell atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Asks the delegate if should always scroll to bottom automatically when new messages are sent or received.
|
||||
*
|
||||
* @return `YES` if you would like to prevent the table view from being scrolled to the bottom while the user is scrolling the table view manually, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)shouldPreventScrollToBottomWhileUserScrolling;
|
||||
|
||||
/**
|
||||
* Ask the delegate if the keyboard should be dismissed by panning/swiping downward. The default value is `YES`. Return `NO` to dismiss the keyboard by tapping.
|
||||
*
|
||||
* @return A boolean value specifying whether the keyboard should be dismissed by panning/swiping.
|
||||
*/
|
||||
- (BOOL)allowsPanToDismissKeyboard;
|
||||
|
||||
/**
|
||||
* Asks the delegate for the send button to be used in messageInputView. Implement this method if you wish to use a custom send button. The button must be a `UIButton` or a subclass of `UIButton`. The button's frame is set for you.
|
||||
*
|
||||
* @return A custom `UIButton` to use in messageInputView.
|
||||
*/
|
||||
- (UIButton *)sendButtonForInputView;
|
||||
|
||||
/**
|
||||
* Asks the delegate for a custom cell reuse identifier for the row to be displayed at the specified index path.
|
||||
*
|
||||
* @param indexPath The index path of the row to be displayed.
|
||||
*
|
||||
* @return A string specifying the cell reuse identifier for the row at indexPath.
|
||||
*/
|
||||
- (NSString *)customCellIdentifierForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@protocol JSMessagesViewDataSource <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
* Asks the data soruce for the message object to display for the row at the specified index path. The message text is displayed in the bubble at index path. The message date is displayed *above* the row at the specified index path. The message sender is displayed *below* the row at the specified index path.
|
||||
*
|
||||
* @param indexPath An index path locating a row in the table view.
|
||||
*
|
||||
* @return An object that conforms to the `JSMessageData` protocol containing the message data. This value must not be `nil`.
|
||||
*/
|
||||
- (id<JSMessageData>)messageForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Asks the data source for the imageView to display for the row at the specified index path with the given sender. The imageView must have its `image` property set.
|
||||
*
|
||||
* @param indexPath An index path locating a row in the table view.
|
||||
* @param sender The name of the user who sent the message at indexPath.
|
||||
*
|
||||
* @return An image view specifying the avatar for the message at indexPath. This value may be `nil`.
|
||||
*/
|
||||
- (UIImageView *)avatarImageViewForRowAtIndexPath:(NSIndexPath *)indexPath sender:(NSString *)sender;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* An instance of `JSMessagesViewController` is a subclass of `UIViewController` specialized to display a messaging interface.
|
||||
*/
|
||||
@interface JSMessagesViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextViewDelegate>
|
||||
|
||||
/**
|
||||
* The object that acts as the delegate of the receiving messages view.
|
||||
*/
|
||||
@property (weak, nonatomic) id<JSMessagesViewDelegate> delegate;
|
||||
|
||||
/**
|
||||
* The object that acts as the data source of receiving messages view.
|
||||
*/
|
||||
@property (weak, nonatomic) id<JSMessagesViewDataSource> dataSource;
|
||||
|
||||
/**
|
||||
* Returns the table view that displays the messages in `JSMessagesViewController`.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) JSMessageTableView *tableView;
|
||||
|
||||
/**
|
||||
* Returns the message input view with which new messages are composed.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) JSMessageInputView *messageInputView;
|
||||
|
||||
/**
|
||||
* The name of the user sending messages. The default value is `nil`.
|
||||
*/
|
||||
@property (copy, nonatomic) NSString *sender;
|
||||
|
||||
#pragma mark - Messages view controller
|
||||
|
||||
/**
|
||||
* Animates and resets the text view in messageInputView. Call this method at the end of the delegate method `didSendText:`.
|
||||
* @see JSMessagesViewDelegate.
|
||||
*/
|
||||
- (void)finishSend;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the background color of the table view, the table view cells, and the table view separator.
|
||||
*
|
||||
* @param color The color to be used as the new background color.
|
||||
*/
|
||||
- (void)setBackgroundColor:(UIColor *)color;
|
||||
|
||||
/**
|
||||
* Scrolls the table view such that the bottom most cell is completely visible, above the messageInputView.
|
||||
*
|
||||
* This method respects the delegate method `shouldPreventScrollToBottomWhileUserScrolling`.
|
||||
*
|
||||
* @see JSMessagesViewDelegate.
|
||||
*
|
||||
* @param animated `YES` if you want to animate scrolling, `NO` if it should be immediate.
|
||||
*/
|
||||
- (void)scrollToBottomAnimated:(BOOL)animated;
|
||||
|
||||
/**
|
||||
* Scrolls the receiver until a row identified by index path is at a particular location on the screen.
|
||||
*
|
||||
* This method respects the delegate method `shouldPreventScrollToBottomWhileUserScrolling`.
|
||||
*
|
||||
* @see JSMessagesViewDelegate.
|
||||
*
|
||||
* @param indexPath An index path that identifies a row in the table view by its row index and its section index.
|
||||
* @param position A constant defined in `UITableViewScrollPosition` that identifies a relative position in the receiving table view.
|
||||
* @param animated `YES` if you want to animate the change in position, `NO` if it should be immediate.
|
||||
*/
|
||||
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
atScrollPosition:(UITableViewScrollPosition)position
|
||||
animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
546
JSMessagesViewController/Classes/JSMessagesViewController.m
Normal file
@ -0,0 +1,546 @@
|
||||
//
|
||||
// Created by Jesse Squires
|
||||
// http://www.hexedbits.com
|
||||
//
|
||||
//
|
||||
// Documentation
|
||||
// http://cocoadocs.org/docsets/JSMessagesViewController
|
||||
//
|
||||
//
|
||||
// The MIT License
|
||||
// Copyright (c) 2013 Jesse Squires
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
#import "JSMessagesViewController.h"
|
||||
#import "JSMessageTextView.h"
|
||||
#import "NSString+JSMessagesView.h"
|
||||
|
||||
@interface JSMessagesViewController () <JSDismissiveTextViewDelegate>
|
||||
|
||||
@property (assign, nonatomic) CGFloat previousTextViewContentHeight;
|
||||
@property (assign, nonatomic) BOOL isUserScrolling;
|
||||
@property (assign, nonatomic) BOOL isObserving;
|
||||
|
||||
- (void)setup;
|
||||
|
||||
- (void)sendPressed:(UIButton *)sender;
|
||||
|
||||
- (void)handleTapGestureRecognizer:(UITapGestureRecognizer *)tap;
|
||||
|
||||
- (BOOL)shouldAllowScroll;
|
||||
|
||||
- (void)layoutAndAnimateMessageInputTextView:(UITextView *)textView;
|
||||
- (void)setTableViewInsetsWithBottomValue:(CGFloat)bottom;
|
||||
- (UIEdgeInsets)tableViewInsetsWithBottomValue:(CGFloat)bottom;
|
||||
|
||||
- (void)handleWillShowKeyboardNotification:(NSNotification *)notification;
|
||||
- (void)handleWillHideKeyboardNotification:(NSNotification *)notification;
|
||||
- (void)keyboardWillShowHide:(NSNotification *)notification;
|
||||
|
||||
- (void)animationForMessageInputViewAtPoint:(CGPoint)point;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation JSMessagesViewController
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (void)setup
|
||||
{
|
||||
if ([self.view isKindOfClass:[UIScrollView class]]) {
|
||||
// FIXME: hack-ish fix for ipad modal form presentations
|
||||
((UIScrollView *)self.view).scrollEnabled = NO;
|
||||
}
|
||||
|
||||
if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) {
|
||||
self.automaticallyAdjustsScrollViewInsets = NO;
|
||||
}
|
||||
|
||||
_isUserScrolling = NO;
|
||||
|
||||
JSMessageInputViewStyle inputViewStyle = [self.delegate inputViewStyle];
|
||||
CGFloat inputViewHeight = (inputViewStyle == JSMessageInputViewStyleFlat) ? 45.0f : 40.0f;
|
||||
|
||||
JSMessageTableView *tableView = [[JSMessageTableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
|
||||
tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
tableView.dataSource = self;
|
||||
tableView.delegate = self;
|
||||
[self.view addSubview:tableView];
|
||||
_tableView = tableView;
|
||||
|
||||
[self setTableViewInsetsWithBottomValue:inputViewHeight];
|
||||
|
||||
[self setBackgroundColor:[UIColor js_backgroundColorClassic]];
|
||||
|
||||
CGRect inputFrame = CGRectMake(0.0f,
|
||||
self.view.frame.size.height - inputViewHeight,
|
||||
self.view.frame.size.width,
|
||||
inputViewHeight);
|
||||
|
||||
BOOL allowsPan = YES;
|
||||
if ([self.delegate respondsToSelector:@selector(allowsPanToDismissKeyboard)]) {
|
||||
allowsPan = [self.delegate allowsPanToDismissKeyboard];
|
||||
}
|
||||
|
||||
UIPanGestureRecognizer *pan = allowsPan ? _tableView.panGestureRecognizer : nil;
|
||||
|
||||
JSMessageInputView *inputView = [[JSMessageInputView alloc] initWithFrame:inputFrame
|
||||
style:inputViewStyle
|
||||
delegate:self
|
||||
panGestureRecognizer:pan];
|
||||
|
||||
if (!allowsPan) {
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGestureRecognizer:)];
|
||||
[_tableView addGestureRecognizer:tap];
|
||||
}
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(sendButtonForInputView)]) {
|
||||
UIButton *sendButton = [self.delegate sendButtonForInputView];
|
||||
[inputView setSendButton:sendButton];
|
||||
}
|
||||
|
||||
inputView.sendButton.enabled = NO;
|
||||
[inputView.sendButton addTarget:self
|
||||
action:@selector(sendPressed:)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[self.view addSubview:inputView];
|
||||
_messageInputView = inputView;
|
||||
}
|
||||
|
||||
#pragma mark - View lifecycle
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
[self setup];
|
||||
[[JSBubbleView appearance] setFont:[UIFont systemFontOfSize:16.0f]];
|
||||
self.isObserving = NO;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
if (!self.isObserving) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleWillShowKeyboardNotification:)
|
||||
name:UIKeyboardWillShowNotification
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleWillHideKeyboardNotification:)
|
||||
name:UIKeyboardWillHideNotification
|
||||
object:nil];
|
||||
|
||||
[self.messageInputView.textView addObserver:self
|
||||
forKeyPath:@"contentSize"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:nil];
|
||||
|
||||
self.isObserving = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self.messageInputView resignFirstResponder];
|
||||
[self setEditing:NO animated:YES];
|
||||
|
||||
if (self.isObserving) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
|
||||
|
||||
[self.messageInputView.textView removeObserver:self forKeyPath:@"contentSize"];
|
||||
|
||||
self.isObserving = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning
|
||||
{
|
||||
[super didReceiveMemoryWarning];
|
||||
NSLog(@"*** %@: didReceiveMemoryWarning ***", [self class]);
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_delegate = nil;
|
||||
_dataSource = nil;
|
||||
_tableView.delegate = nil;
|
||||
_tableView.dataSource = nil;
|
||||
_tableView = nil;
|
||||
_messageInputView = 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
|
||||
{
|
||||
// add a space to accept any auto-correct suggestions
|
||||
NSString *text = self.messageInputView.textView.text;
|
||||
self.messageInputView.textView.text = [text stringByAppendingString:@" "];
|
||||
|
||||
[self.delegate didSendText:[self.messageInputView.textView.text js_stringByTrimingWhitespace]
|
||||
fromSender:self.sender
|
||||
onDate:[NSDate date]];
|
||||
}
|
||||
|
||||
- (void)handleTapGestureRecognizer:(UITapGestureRecognizer *)tap
|
||||
{
|
||||
[self.messageInputView.textView resignFirstResponder];
|
||||
}
|
||||
|
||||
#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];
|
||||
|
||||
UIImageView *bubbleImageView = [self.delegate bubbleImageViewWithType:type
|
||||
forRowAtIndexPath:indexPath];
|
||||
|
||||
id<JSMessageData> message = [self.dataSource messageForRowAtIndexPath:indexPath];
|
||||
|
||||
UIImageView *avatar = [self.dataSource avatarImageViewForRowAtIndexPath:indexPath sender:[message sender]];
|
||||
|
||||
BOOL displayTimestamp = YES;
|
||||
if ([self.delegate respondsToSelector:@selector(shouldDisplayTimestampForRowAtIndexPath:)]) {
|
||||
displayTimestamp = [self.delegate shouldDisplayTimestampForRowAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
NSString *CellIdentifier = nil;
|
||||
if ([self.delegate respondsToSelector:@selector(customCellIdentifierForRowAtIndexPath:)]) {
|
||||
CellIdentifier = [self.delegate customCellIdentifierForRowAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
if (!CellIdentifier) {
|
||||
CellIdentifier = [NSString stringWithFormat:@"JSMessageCell_%d_%d_%d_%d", (int)type, displayTimestamp, avatar != nil, [message sender] != nil];
|
||||
}
|
||||
|
||||
JSBubbleMessageCell *cell = (JSBubbleMessageCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
|
||||
|
||||
if (!cell) {
|
||||
cell = [[JSBubbleMessageCell alloc] initWithBubbleType:type
|
||||
bubbleImageView:bubbleImageView
|
||||
message:message
|
||||
displaysTimestamp:displayTimestamp
|
||||
hasAvatar:avatar != nil
|
||||
reuseIdentifier:CellIdentifier];
|
||||
}
|
||||
|
||||
[cell setMessage:message];
|
||||
[cell setAvatarImageView:avatar];
|
||||
[cell setBackgroundColor:tableView.backgroundColor];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(configureCell:atIndexPath:)]) {
|
||||
[self.delegate configureCell:cell atIndexPath:indexPath];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - Table view delegate
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<JSMessageData> message = [self.dataSource messageForRowAtIndexPath:indexPath];
|
||||
UIImageView *avatar = [self.dataSource avatarImageViewForRowAtIndexPath:indexPath sender:[message sender]];
|
||||
|
||||
BOOL displayTimestamp = YES;
|
||||
if ([self.delegate respondsToSelector:@selector(shouldDisplayTimestampForRowAtIndexPath:)]) {
|
||||
displayTimestamp = [self.delegate shouldDisplayTimestampForRowAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
return [JSBubbleMessageCell neededHeightForBubbleMessageCellWithMessage:message
|
||||
displaysAvatar:avatar != nil
|
||||
displaysTimestamp:displayTimestamp];
|
||||
}
|
||||
|
||||
#pragma mark - Messages view controller
|
||||
|
||||
- (void)finishSend
|
||||
{
|
||||
[self.messageInputView.textView setText:nil];
|
||||
[self.messageInputView.textView.undoManager removeAllActions];
|
||||
[self textViewDidChange:self.messageInputView.textView];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)setBackgroundColor:(UIColor *)color
|
||||
{
|
||||
self.view.backgroundColor = color;
|
||||
_tableView.backgroundColor = color;
|
||||
_tableView.separatorColor = color;
|
||||
}
|
||||
|
||||
- (void)scrollToBottomAnimated:(BOOL)animated
|
||||
{
|
||||
if (![self shouldAllowScroll])
|
||||
return;
|
||||
|
||||
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
|
||||
{
|
||||
if (![self shouldAllowScroll])
|
||||
return;
|
||||
|
||||
[self.tableView scrollToRowAtIndexPath:indexPath
|
||||
atScrollPosition:position
|
||||
animated:animated];
|
||||
}
|
||||
|
||||
- (BOOL)shouldAllowScroll
|
||||
{
|
||||
if (self.isUserScrolling) {
|
||||
if ([self.delegate respondsToSelector:@selector(shouldPreventScrollToBottomWhileUserScrolling)]
|
||||
&& [self.delegate shouldPreventScrollToBottomWhileUserScrolling]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Scroll view delegate
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
||||
{
|
||||
self.isUserScrolling = YES;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
|
||||
{
|
||||
self.isUserScrolling = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Text view delegate
|
||||
|
||||
- (void)textViewDidBeginEditing:(UITextView *)textView
|
||||
{
|
||||
[textView becomeFirstResponder];
|
||||
|
||||
if (!self.previousTextViewContentHeight)
|
||||
self.previousTextViewContentHeight = textView.contentSize.height;
|
||||
|
||||
[self scrollToBottomAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)textView
|
||||
{
|
||||
self.messageInputView.sendButton.enabled = ([[textView.text js_stringByTrimingWhitespace] length] > 0);
|
||||
}
|
||||
|
||||
- (void)textViewDidEndEditing:(UITextView *)textView
|
||||
{
|
||||
[textView resignFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - Layout message input view
|
||||
|
||||
- (void)layoutAndAnimateMessageInputTextView:(UITextView *)textView
|
||||
{
|
||||
CGFloat maxHeight = [JSMessageInputView maxHeight];
|
||||
|
||||
BOOL isShrinking = textView.contentSize.height < self.previousTextViewContentHeight;
|
||||
CGFloat changeInHeight = textView.contentSize.height - self.previousTextViewContentHeight;
|
||||
|
||||
if (!isShrinking && (self.previousTextViewContentHeight == maxHeight || textView.text.length == 0)) {
|
||||
changeInHeight = 0;
|
||||
}
|
||||
else {
|
||||
changeInHeight = MIN(changeInHeight, maxHeight - self.previousTextViewContentHeight);
|
||||
}
|
||||
|
||||
if (changeInHeight != 0.0f) {
|
||||
[UIView animateWithDuration:0.25f
|
||||
animations:^{
|
||||
[self setTableViewInsetsWithBottomValue:self.tableView.contentInset.bottom + changeInHeight];
|
||||
|
||||
[self scrollToBottomAnimated:NO];
|
||||
|
||||
if (isShrinking) {
|
||||
// if shrinking the view, animate text view frame BEFORE input view frame
|
||||
[self.messageInputView adjustTextViewHeightBy:changeInHeight];
|
||||
}
|
||||
|
||||
CGRect inputViewFrame = self.messageInputView.frame;
|
||||
self.messageInputView.frame = CGRectMake(0.0f,
|
||||
inputViewFrame.origin.y - changeInHeight,
|
||||
inputViewFrame.size.width,
|
||||
inputViewFrame.size.height + changeInHeight);
|
||||
|
||||
if (!isShrinking) {
|
||||
// growing the view, animate the text view frame AFTER input view frame
|
||||
[self.messageInputView adjustTextViewHeightBy:changeInHeight];
|
||||
}
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
}];
|
||||
|
||||
self.previousTextViewContentHeight = MIN(textView.contentSize.height, maxHeight);
|
||||
}
|
||||
|
||||
// Once we reached the max height, we have to consider the bottom offset for the text view.
|
||||
// To make visible the last line, again we have to set the content offset.
|
||||
if (self.previousTextViewContentHeight == maxHeight) {
|
||||
double delayInSeconds = 0.01;
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
|
||||
dispatch_after(popTime,
|
||||
dispatch_get_main_queue(),
|
||||
^(void) {
|
||||
CGPoint bottomOffset = CGPointMake(0.0f, textView.contentSize.height - textView.bounds.size.height);
|
||||
[textView setContentOffset:bottomOffset animated:YES];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTableViewInsetsWithBottomValue:(CGFloat)bottom
|
||||
{
|
||||
UIEdgeInsets insets = [self tableViewInsetsWithBottomValue:bottom];
|
||||
self.tableView.contentInset = insets;
|
||||
self.tableView.scrollIndicatorInsets = insets;
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)tableViewInsetsWithBottomValue:(CGFloat)bottom
|
||||
{
|
||||
UIEdgeInsets insets = UIEdgeInsetsZero;
|
||||
|
||||
if ([self respondsToSelector:@selector(topLayoutGuide)]) {
|
||||
insets.top = self.topLayoutGuide.length;
|
||||
}
|
||||
|
||||
insets.bottom = bottom;
|
||||
|
||||
return insets;
|
||||
}
|
||||
|
||||
#pragma mark - Key-value observing
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
if (object == self.messageInputView.textView && [keyPath isEqualToString:@"contentSize"]) {
|
||||
[self layoutAndAnimateMessageInputTextView:object];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard notifications
|
||||
|
||||
- (void)handleWillShowKeyboardNotification:(NSNotification *)notification
|
||||
{
|
||||
[self keyboardWillShowHide:notification];
|
||||
}
|
||||
|
||||
- (void)handleWillHideKeyboardNotification:(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];
|
||||
|
||||
NSInteger animationCurveOption = (curve << 16);
|
||||
|
||||
[UIView animateWithDuration:duration
|
||||
delay:0.0
|
||||
options:animationCurveOption
|
||||
animations:^{
|
||||
CGFloat keyboardY = [self.view convertRect:keyboardRect fromView:nil].origin.y;
|
||||
|
||||
CGRect inputViewFrame = self.messageInputView.frame;
|
||||
CGFloat inputViewFrameY = keyboardY - inputViewFrame.size.height;
|
||||
|
||||
// for ipad modal form presentations
|
||||
CGFloat messageViewFrameBottom = self.view.frame.size.height - inputViewFrame.size.height;
|
||||
if (inputViewFrameY > messageViewFrameBottom)
|
||||
inputViewFrameY = messageViewFrameBottom;
|
||||
|
||||
self.messageInputView.frame = CGRectMake(inputViewFrame.origin.x,
|
||||
inputViewFrameY,
|
||||
inputViewFrame.size.width,
|
||||
inputViewFrame.size.height);
|
||||
|
||||
[self setTableViewInsetsWithBottomValue:self.view.frame.size.height
|
||||
- self.messageInputView.frame.origin.y];
|
||||
}
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Dismissive text view delegate
|
||||
|
||||
- (void)keyboardDidScrollToPoint:(CGPoint)point
|
||||
{
|
||||
[self animationForMessageInputViewAtPoint:point];
|
||||
}
|
||||
|
||||
- (void)keyboardWillSnapBackToPoint:(CGPoint)point
|
||||
{
|
||||
[self animationForMessageInputViewAtPoint:point];
|
||||
}
|
||||
|
||||
- (void)keyboardWillBeDismissed
|
||||
{
|
||||
CGRect inputViewFrame = self.messageInputView.frame;
|
||||
inputViewFrame.origin.y = self.view.bounds.size.height - inputViewFrame.size.height;
|
||||
self.messageInputView.frame = inputViewFrame;
|
||||
}
|
||||
|
||||
- (void)animationForMessageInputViewAtPoint:(CGPoint)point
|
||||
{
|
||||
CGRect inputViewFrame = self.messageInputView.frame;
|
||||
CGPoint keyboardOrigin = [self.view convertPoint:point fromView:nil];
|
||||
inputViewFrame.origin.y = keyboardOrigin.y - inputViewFrame.size.height;
|
||||
self.messageInputView.frame = inputViewFrame;
|
||||
}
|
||||
|
||||
@end
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 421 B |