Compare commits

..

13 Commits

Author SHA1 Message Date
Jesse Squires
309329cc0c Merge pull request #580 from uversity/iOS6_support_stable
Fixed dependency on JSQSystemSoundPlayer for iOS6_support_stable
2014-10-24 14:20:59 -07:00
Daniel Resnick
92d13ae31e Fixed dependency on systemsoundplayer 2014-10-24 11:21:10 -07:00
Jesse Squires
351118b20e Merge pull request #507 from alr/iOS6_support_stable
fix crash when user use Undo after sending text message
2014-09-25 16:26:51 -07:00
Dmitry Abolmasov
08bcc96f08 fix crash when user use Undo after sending text message 2014-09-25 22:56:02 +04:00
Jesse Squires
420c643f86 Merge pull request #350 from jcoleman/fix-kvo-issues-on-iOS6_support_stable
Fix kvo issues on iOS6_support_stable
2014-08-25 12:59:27 -07:00
James Coleman
03ac5832c9 Rename viewWillAppearHasSetupObservers to isObserving. 2014-08-25 13:51:52 -04:00
James Coleman
5d5d6a410b Use property rather than ivar for viewWillAppearHasSetupObservers. 2014-08-25 09:04:47 -04:00
James Coleman
68ec26479f Fix KVO registration/deregistration issues.
viewWillAppear and viewWillDisappear are not guaranteed to happen in any particular order; the only guarantees are that they will be called on appearance/disappearance. That is, viewWillDisappear, for example, can be called more than once. This means that for proper KVO API conformance, to register/deregister in these methods you need to keep track of whether or not you're currently observing. This fixes #180, fixes #268, fixes the issues caused by #131 and the fix in #177 that was later reverted.
2014-06-09 10:57:07 -04:00
Jesse Squires
bf4a2a7525 Merge pull request #247 from xhzengAIB/master
fix iOS7 scroll to bottom issue in v4.0
2014-04-28 08:59:15 -07:00
Jack
666eace95b Just fix the iOS7 new api. 2014-04-28 13:54:18 +08:00
Jack
3a7e8b13dc refactor method. 2014-04-28 13:01:52 +08:00
Jack
eb485f2184 fix the bug from iOS7 new api. 2014-04-28 12:55:03 +08:00
Jack
b81cfae779 fix textView the bug from iOS7. 2014-04-27 13:08:41 +08:00
341 changed files with 4896 additions and 16439 deletions

View File

@ -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"

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +0,0 @@
## How To Contribute
Please follow these sweet [contribution guidelines](https://github.com/jessesquires/HowToContribute).

View 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 */;
}

View File

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:JSQMessages.xcodeproj">
location = "self:JSMessagesDemo.xcodeproj">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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"
}
],

View File

@ -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" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -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" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -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" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -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"
}
],

View 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

View 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

View 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

View 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

View File

@ -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>

View 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

View File

@ -0,0 +1,2 @@
/* Localized versions of Info.plist keys */

18
JSMessagesDemo/main.m Normal file
View 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]));
}
}

View File

@ -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>

View 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

View File

@ -0,0 +1,2 @@
/* Localized versions of Info.plist keys */

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View 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 "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

View File

@ -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 applications 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 applications 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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Some files were not shown because too many files have changed in this diff Show More