Compare commits

...

130 Commits

Author SHA1 Message Date
Michael Kirk
1de1300f2a JSignalQMessageViewController deprecated 2017-02-09 16:51:20 -05:00
Michael Kirk
225b1baa11 Merge pull request #7 from WhisperSystems/fix-crash-on-delete
Fix crash after delete
2016-06-15 09:04:36 -07:00
Sebastian Ludwig
563f505da4 Fixed #1026 by avoiding overreleasing the sender (the tapped message view cell) of a custom context menu action. 2016-06-15 08:59:42 -07:00
Frederic Jacobs
e5582fef8a Message hashes for DisplayedMessages 2015-06-07 22:25:19 +02:00
Frederic Jacobs
533b4bef6c Merge fixes 2015-06-07 15:18:02 +02:00
Jesse Squires
867115b03c update spec 2015-05-31 11:24:29 -07:00
Jesse Squires
c2651dd0a4 pod update 2015-05-31 11:20:33 -07:00
Jesse Squires
ec8dcf66d5 Update LICENSE 2015-05-31 11:18:33 -07:00
Jesse Squires
acbb692b3d Update README.md 2015-05-31 11:18:17 -07:00
Jesse Squires
7606339770 pod update 2015-05-26 19:45:13 -07:00
Jesse Squires
9551159a20 Merge branch 'develop' of https://github.com/jessesquires/JSQMessagesViewController into develop 2015-05-26 19:39:24 -07:00
Jesse Squires
c8831f0b30 update ignore. include Pods/ 2015-05-26 19:39:11 -07:00
Jesse Squires
566ddb3301 Merge pull request #996 from sumihiro/localization_ja
Add Japanese Localization
2015-05-21 22:13:33 -07:00
sumihiro
ea373b0130 Add Japanese Localization 2015-05-22 09:52:58 +09:00
Frederic Jacobs
5ac93a25e3 Merge branch 'master' of github.com:jessesquires/JSQMessagesViewController into JSignalQ 2015-05-17 19:35:16 +02:00
Jesse Squires
8e8f0998be clean up and refinements for PR #950 and #756. issue #323. move maximumInputToolbarHeight to toolbar class. 2015-05-12 18:32:05 -07:00
Jesse Squires
27ec773edc Merge pull request #950 from werediver/develop-lbanders-extra
bug fix follow-up on issue #323
2015-05-12 18:05:24 -07:00
Jesse Squires
34f046c452 Merge pull request #756 from lbanders/develop
Add a maxToolbarHeight property to. fix #323
2015-05-12 18:00:58 -07:00
Jesse Squires
7e7edbf675 pod update 2015-05-11 18:06:19 -07:00
Jesse Squires
98368fc105 Merge pull request #973 from RQEBMM/issue_971_makeKeyboardControllerPublic
made keyboardController a public property. close #971
2015-05-07 11:43:56 -07:00
Benjamin McCloskey
08567a4be6 made keyboardController a public property 2015-05-07 12:52:07 -05:00
Jesse Squires
78c46c0ac4 Merge pull request #972 from vani2/bazar_app_link
Add link to Bazar app
2015-05-07 09:47:38 -07:00
Ivan Vavilov
2f94744368 Add link to Bazar app 2015-05-07 10:26:00 +03:00
Jesse Squires
ad761def43 more refinements from PR #811 2015-05-06 10:19:37 -07:00
Jesse Squires
c5c9c20246 clean up and refinements from PR #811 2015-05-06 10:05:09 -07:00
Jesse Squires
c4d38a8407 Merge pull request #811 from sebastianludwig/develop
Implemented #810: An easier way to handle custom menu actions. close #810
2015-05-06 09:27:10 -07:00
Jesse Squires
9db018bfce clear cached media views. close #754 2015-05-06 09:18:26 -07:00
Jesse Squires
0db2ab9e74 pod update 2015-05-05 16:48:32 -07:00
Jesse Squires
6a5aeb5ffb Merge branch 'develop' of https://github.com/jessesquires/JSQMessagesViewController into develop 2015-05-05 14:46:03 -07:00
Jesse Squires
74acd93417 bump version nums 2015-05-05 14:45:54 -07:00
Jesse Squires
3247b45b13 Update README.md 2015-05-04 21:38:04 -07:00
Jesse Squires
bccad24efc Merge pull request #951 from tobiasoleary/remove_context_state_save_restore
Remove unneeded CGContext Save and Restore calls.
2015-05-04 16:17:48 -07:00
Tobias O'Leary
5e9e368f4d Remove unneeded CGContext Save and Restore calls.
In the private class methods of JSQMessagesAvatarImageFactory
(jsq_imageWitInitials, jsq_circularImage) there were calls to save and restore
the state of the graphic context.

The calls are not needed because UIGraphicsBeginImageContext creates a new
context in a default state.
2015-04-25 13:19:30 -04:00
Roman Fedoseyev
bff4ae7b86 #323 / #726 Possible crash caused by AutoLayout engine fault on iOS 7.x fixed.
The problem itself can be observed when the pull request #726 is applyed on top of v7.0.1+.
2015-04-25 16:13:17 +03:00
Jesse Squires
4c05c28f70 Update README.md 2015-04-24 08:59:35 -07:00
Jesse Squires
6b58a211c5 Merge pull request #938 from bearclough/issue_937_fix
Fix Issue #937: Updated implementation of UICollectionViewDelegate
2015-04-20 21:59:52 -07:00
bearclough
cb901cb026 Updated JSQMessagesViewController's implementation of UICollectionViewDelegate perform action for item to use the collectionView's dataSource instead of JSQMessagesViewController's 2015-04-20 21:10:18 -07:00
Jesse Squires
cd974aa015 suppress warning for now. fix in next release, see #920 2015-04-19 11:38:37 -07:00
Jesse Squires
a6a2884b07 bump version nums 2015-04-19 11:20:00 -07:00
Jesse Squires
a859a6862a fix toolbar button constraint issues. close #460 2015-04-19 11:03:47 -07:00
Jesse Squires
72e7343908 Merge pull request #888 from danhbear/issue_887_fix_springy_insert_sizezero
Don't return spring behavior for attributes with empty size. close #887
2015-04-19 10:46:39 -07:00
Jesse Squires
45590c3405 Merge branch 'develop' of https://github.com/jessesquires/JSQMessagesViewController into develop 2015-04-19 10:39:15 -07:00
Jesse Squires
cd085c683f Merge pull request #880 from erysaj/fix-toolbar-dragging
Fix toolbar dragging. close #666
2015-04-19 10:39:10 -07:00
Jesse Squires
853a3fe1c5 pod update 2015-04-19 10:30:22 -07:00
Daniel Bear
7610526965 PR style feedback
https://github.com/jessesquires/JSQMessagesViewController/pull/888/files#r28034689
2015-04-10 08:17:06 -07:00
Jesse Squires
ab831aaf67 fix demo contraints 2015-04-08 21:21:09 -07:00
Jesse Squires
5c57cee47f Merge pull request #889 from erysaj/fix-segfault-crashes
Fix segfault crashes
2015-04-08 21:14:56 -07:00
Jesse Squires
dcebef9d1e Merge pull request #915 from luosheng/develop
Revert @import for #import.
2015-04-08 21:08:22 -07:00
Luo Sheng
547cec7287 Revert @import for #import. 2015-04-09 11:51:31 +08:00
Eugene Rysaj
72540e2553 Nullify delegates to prevent crashes 2015-03-30 14:04:02 +03:00
Daniel Bear
1c5811107c Don't return spring behavior for attributes with empty size.
The demo project currently displays a new outgoing message using JSQMessagesViewController -finishSendingMessageAnimated:, which calls UICollectionView -reloadData. This works fine. However when new items are instead added using UICollectionView -insertItemsAtIndexPaths:, a failure occurs in the flow layout when trying to add the spring behavior to the UIDynamicAnimator in -prepareForCollectionViewUpdates:. This change protects against that failure, which is caused by an invalid zero size in the layout attributes. Adding a nil behavior instead behaves correctly.
2015-03-27 15:15:12 -07:00
Jesse Squires
ea42262a9a Merge branch 'develop' of https://github.com/jessesquires/JSQMessagesViewController into develop 2015-03-26 21:41:51 -07:00
Jesse Squires
483f36402a pod update 2015-03-26 21:41:45 -07:00
Eugene Rysaj
7edc1312d1 fix detecting pre-iOS8 devices (did not work on iOS 8.0) 2015-03-24 15:15:08 +02:00
Eugene Rysaj
6086d26bc8 fix toolbar going out of sight when slowly dragging down and back in landscape orientation (iOS 7.x) 2015-03-24 15:00:19 +02:00
Leif Bredgaard Honore
e75c2fb166 Merge errors fixed 2015-03-22 10:55:13 +04:00
Leif Bredgaard Honore
0ed53ae8c5 Merge remote-tracking branch 'jessesquires/develop' into develop
Conflicts:
	JSQMessagesViewController/Controllers/JSQMessagesViewController.m
2015-03-22 08:12:56 +04:00
Jesse Squires
fb7119d3b2 Update README.md 2015-03-21 11:36:06 -07:00
Jesse Squires
71a7f519fc Update README.md 2015-03-21 11:34:13 -07:00
Frederic Jacobs
9be8f6ed65 Bringing call translations out of JSQ bundle 2015-03-21 15:12:49 +01:00
Frederic Jacobs
2beab5be52 Merge fixes. 2015-03-21 15:12:42 +01:00
Frederic Jacobs
10e1fc80a4 f 2015-03-21 14:43:08 +01:00
Jesse Squires
62c6844ce6 Update README.md 2015-03-20 21:53:38 -07:00
Leif Bredgaard Honore
dd0e214d13 #323 updated according to comments by @jessesquires - Also tests is now included 2015-03-19 10:39:31 +04:00
Jesse Squires
25a887a31e Merge pull request #867 from jeffreyjackson/patch-1
Update README.md
2015-03-17 14:40:47 -07:00
Jeffrey Jackson
bd82c11af6 Update README.md 2015-03-17 16:21:42 -04:00
Jesse Squires
3e69f1fc1d clean up from PR #841 2015-03-16 20:06:53 -07:00
Jesse Squires
acb047f25d Merge pull request #841 from jardamach/develop
Keyboard scrolling problem & fix of toolbar position while panning. fix #666. fix #405.
2015-03-16 20:06:01 -07:00
Jesse Squires
fc581c8b0b update and refine spec and resource loading. 2015-03-16 19:29:53 -07:00
Jesse Squires
007a945959 improve scrolling performance on iOS 8. #492 2015-03-16 18:37:23 -07:00
Jesse Squires
56f8a9c648 update spec. bump version numbers. 2015-03-16 18:27:48 -07:00
Jesse Squires
0f5cd6c2fd move strings to bundle. add bundle helper methods. #784 2015-03-16 18:19:29 -07:00
Jesse Squires
86746bd9d6 refine strings and keys 2015-03-16 17:13:31 -07:00
Jesse Squires
94b19ccb08 fix broken tests. derp. 2015-03-16 16:11:38 -07:00
Jesse Squires
88d2fe4ef4 update travis script 2015-03-16 15:34:17 -07:00
Jesse Squires
97bfef24c9 remove UIScrollViewKeyboardDismissModeInteractive. ref #666 2015-03-16 15:16:16 -07:00
Jesse Squires
f09dc0230e another fix for #684 2015-03-16 15:10:17 -07:00
Jesse Squires
a30f9bdee9 rename hash protocol method names in JSQMessageData and JSQMessageMediaData. fixes swift compatibility and core data issues. close #684 2015-03-16 15:07:56 -07:00
Jesse Squires
e23a248797 Fix #818 2015-03-16 14:56:58 -07:00
Jesse Squires
05c97700a2 don't upperCase string for avatarImageWithUserInitials. leave this up to the caller. close #755 2015-03-16 14:43:58 -07:00
Jesse Squires
1f64013617 Rename 'emptyCache' property on invalidationContext. close #661 2015-03-16 14:34:20 -07:00
Jesse Squires
a6437be1ab fix warnings from Xcode 6.3 beta 2015-03-16 14:26:50 -07:00
Jesse Squires
e0ae6cdb1f Update README.md 2015-03-06 18:05:46 -08:00
jardamach
d9aea32ed0 Keyboard scrolling problem & fix of toolbar position while panning #666 2015-03-06 13:59:04 +01:00
Jesse Squires
09db017b87 Update README.md 2015-03-02 12:02:59 -08:00
Jesse Squires
a180dcbee6 Update README.md 2015-02-26 21:45:47 -08:00
Jesse Squires
2698a699d8 Update README.md 2015-02-26 21:41:29 -08:00
Jesse Squires
0aa0ae2237 fix #791 2015-02-26 19:27:54 -08:00
Jesse Squires
1fb791f964 Merge branch 'develop' of https://github.com/jessesquires/JSQMessagesViewController into develop 2015-02-26 19:14:08 -08:00
Jesse Squires
0b8fb267d1 Fix #613 2015-02-26 19:13:53 -08:00
Sebastian Ludwig
b728ff3b4a Implemented #810: An easier way to handle custom menu actions. 2015-02-21 15:17:41 +07:00
Jesse Squires
fa7e42bfa5 Update README.md 2015-02-20 10:44:12 -08:00
Frederic Jacobs
bfb0c58785 Fixing line height of warnings 2015-02-16 23:23:40 +01:00
Frederic Jacobs
c0a35d832b Merging upstream 2015-02-16 23:01:32 +01:00
Jesse Squires
82534ea75d update tests 2015-02-08 02:42:35 -08:00
Jesse Squires
08cc2d14d4 remove default values from senderDisplayName and senderId. add parameterAsserts instead. #774 2015-02-08 02:41:21 -08:00
Jesse Squires
720999efe9 oops. fix JSQMessagesCollectionViewCell. #773 2015-02-07 11:40:55 -08:00
Jesse Squires
0157f1a47e more fixes for cocoapods framework support. close #773 2015-02-07 11:36:49 -08:00
Jesse Squires
fc340cd166 fix spelling error. close #748 2015-02-05 22:42:27 -08:00
Jesse Squires
399544ff5d use CG geometry functions because goddammit. 2015-02-05 22:26:10 -08:00
Jesse Squires
245551f8ce revert changes from #702. causes other side effects. re-open #450. fuck keyboards. fuck this shitty keyboard API. and fuck iOS 8.1.3 goddammit. more beer please. 2015-02-05 22:25:37 -08:00
Jesse Squires
27d7a2e09c revert changes from PR #646, fix #706. PR #646 was possibly solved by #739. ref: #763 2015-02-05 21:57:48 -08:00
Jesse Squires
9e6e0f2524 refactor changes from #739 2015-02-05 21:48:32 -08:00
Jesse Squires
9893d4208f Merge pull request #739 from doggy/issue_#738_Fix
reset keyboard view status for pan gesture. close #738
2015-02-05 21:44:08 -08:00
Jesse Squires
3e80be42db refactor and clean up from PR #751 2015-02-05 21:19:15 -08:00
Jesse Squires
e354981b49 Merge pull request #751 from walsh2000/develop
Making JSQMessagesInputToolbar easier to subclass. Close #750
2015-02-05 21:04:52 -08:00
Jesse Squires
08377bc153 Merge branch 'develop' of https://github.com/jessesquires/JSQMessagesViewController into develop 2015-02-05 20:53:14 -08:00
Jesse Squires
1acb68119e close #702, fix #701, fix #450 2015-02-05 20:53:02 -08:00
Jesse Squires
f31b214c1b set header load button font 2015-02-05 20:41:58 -08:00
Jesse Squires
ddf11e62e4 Update README.md
add link to cocoapods. close #770
2015-02-05 09:23:40 -08:00
Leif Bredgaard Honore
fa668ef7da #323 fixed by adding a maxToolbarHeight property to JSQMessageViewController 2015-01-22 11:35:29 +04:00
Raymond Walsh
e3a2babfb2 move added code lower in the file
instead of adding it above awakeFromNib, place new functions near the bottom
2015-01-20 15:23:36 -08:00
Raymond Walsh
ed81bb2977 Modify JSQMessagesInputToolbar to make it easier to subclass
1. Toolbar instantiates the contentView through a method (which subclasses can override)
    2. JSQMessagesViewController gets default height from an accessor in JSQMessagesInputToolbar, instead of using a constant directly
2015-01-20 15:17:48 -08:00
doggy
6e7647d14a reset keyboard view status while the finger slide down to the outside of screen 2015-01-16 13:51:39 +08:00
Jesse Squires
99977d4938 bump version numbers 2015-01-10 16:02:10 -08:00
Jesse Squires
df26f3cc16 oops. fix use of iOS 8-only API. 2015-01-10 16:01:25 -08:00
Jesse Squires
1b9814ee5c bump version numbers 2015-01-10 15:33:22 -08:00
Jesse Squires
eec8ff4236 remove references to main bundle for cocoapods 0.36. close #698. 2015-01-10 15:22:22 -08:00
Jesse Squires
3779613da8 fix layout issues. close #722 2015-01-10 14:49:47 -08:00
Jesse Squires
f55bdad953 fix image copy, photo rotation issue. close #723 2015-01-10 14:41:49 -08:00
Jesse Squires
741f53f793 set cell default background color to clearColor. close #727 2015-01-10 14:39:28 -08:00
Jesse Squires
9bbdffa568 Merge branch 'develop' of https://github.com/jessesquires/JSQMessagesViewController into develop 2015-01-10 14:37:16 -08:00
Jesse Squires
db40c8b588 fix typing indicator rotation bug. close #712 2015-01-10 14:37:11 -08:00
Jesse Squires
dbad9a9325 update pods 2015-01-10 14:27:58 -08:00
Jesse Squires
5d21d699f6 Merge pull request #721 from ranunez/develop
Updated iOS version adoption statistics in README
2015-01-07 11:00:03 -08:00
Ricardo Nunez
b5ca3df47e Updated iOS version adoption statistics in README 2015-01-07 08:02:34 -08:00
Jesse Squires
191d2c3700 clean up 2014-12-24 14:35:10 -05:00
Jesse Squires
bbbb242521 Merge pull request #692 from asxasxasx/master
Limit input toolbar according to top inset
2014-12-24 14:29:40 -05:00
asxasxasx
174cae9b6c Limit input toolbar according to top inset
Limit input toolbar according to topContentAdditionalInset value. Fixed issue when textView expands under top view above collection view
2014-12-24 21:32:12 +06:00
Jesse Squires
7d6534eeae fix tests for travis-ci 2014-12-19 18:44:31 -05:00
210 changed files with 8547 additions and 736 deletions

9
.gitignore vendored
View File

@ -1,6 +1,6 @@
# Xcode
.DS_Store
# Xcode
/build/*
*/build/*
*.pbxuser
@ -18,8 +18,3 @@ DerivedData
.idea/
*.hmap
*.xccheckout
#CocoaPods
Pods

View File

@ -7,4 +7,4 @@ before_install:
- if brew outdated | grep -qx xctool; then brew upgrade xctool; fi
script:
- xctool clean build test -workspace JSQMessages.xcworkspace -scheme JSQMessages -sdk iphonesimulator7.1 ONLY_ACTIVE_ARCH=NO
- xctool clean build test -workspace JSQMessages.xcworkspace -scheme JSQMessages -sdk iphonesimulator8.1 ONLY_ACTIVE_ARCH=NO

View File

@ -30,6 +30,8 @@
886C33FD19F4371E006B4997 /* JSQVideoMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 886C33FC19F4371E006B4997 /* JSQVideoMediaItem.m */; };
886C33FF19F45E30006B4997 /* JSQMessagesViewController.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 886C33FE19F45E30006B4997 /* JSQMessagesViewController.podspec */; };
886FFD2E19E9A65D00EB8485 /* UIDevice+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 886FFD2D19E9A65D00EB8485 /* UIDevice+JSQMessages.m */; };
8873B60C1AB7B244006DF9AC /* NSBundle+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 8873B60B1AB7B244006DF9AC /* NSBundle+JSQMessages.m */; };
8873B60E1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8873B60D1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m */; };
8885734A19DE540400E89D20 /* DemoSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8885734919DE540400E89D20 /* DemoSettingsViewController.m */; };
8885734D19DE55D000E89D20 /* NSUserDefaults+DemoSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 8885734C19DE55D000E89D20 /* NSUserDefaults+DemoSettings.m */; };
88A25F3719D8DF2500924534 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F2D19D8DF2500924534 /* AppDelegate.m */; };
@ -101,7 +103,6 @@
88C00A501A44D4D800B004B3 /* JSQPhotoMediaItemTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C00A4F1A44D4D800B004B3 /* JSQPhotoMediaItemTests.m */; };
88C00A521A44D4E500B004B3 /* JSQVideoMediaItemTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C00A511A44D4E500B004B3 /* JSQVideoMediaItemTests.m */; };
88C4583019F5F7A0008FD427 /* JSQMessagesMediaViewBubbleImageMasker.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C4582F19F5F7A0008FD427 /* JSQMessagesMediaViewBubbleImageMasker.m */; };
88E4D7131A0DBD6B000CC061 /* JSQMessages.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8841B88719F4988800EA16B6 /* JSQMessages.strings */; };
94A4FA20C2FBD0D62614D5A8 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 97E6750B77E8A7042BA0754B /* libPods.a */; };
FC15B7A91A1E880900F59801 /* JSQCallCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = FC15B7A81A1E880900F59801 /* JSQCallCollectionViewCell.xib */; };
FC15B7B01A1F6AC800F59801 /* JSQMessagesCollectionViewCellIncoming.xib in Resources */ = {isa = PBXBuildFile; fileRef = FC15B7AE1A1F6AC800F59801 /* JSQMessagesCollectionViewCellIncoming.xib */; };
@ -142,21 +143,6 @@
88324C3319F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaViewBubbleImageMaskerTests.m; sourceTree = "<group>"; };
883C11761A09FB100092A16D /* JSQMessagesCellTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCellTextView.h; sourceTree = "<group>"; };
883C11771A09FB100092A16D /* JSQMessagesCellTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCellTextView.m; sourceTree = "<group>"; };
8841B88619F4988800EA16B6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B88819F4988900EA16B6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B88919F4988A00EA16B6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B88A19F4988B00EA16B6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B88B19F4988C00EA16B6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/JSQMessages.strings"; sourceTree = "<group>"; };
8841B88C19F4988F00EA16B6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/JSQMessages.strings"; sourceTree = "<group>"; };
8841B88D19F4989000EA16B6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B88E19F4989100EA16B6 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B88F19F4989200EA16B6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B89019F4989200EA16B6 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B89119F4989300EA16B6 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B89219F4989400EA16B6 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B89319F4989500EA16B6 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B89419F4989500EA16B6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/JSQMessages.strings; sourceTree = "<group>"; };
8841B89519F4989600EA16B6 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/JSQMessages.strings; sourceTree = "<group>"; };
88445B3019E0AE3F0014F889 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
88445B3219E0AE450014F889 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
88445B3419E0AE4A0014F889 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@ -172,6 +158,9 @@
886C33FE19F45E30006B4997 /* JSQMessagesViewController.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = JSQMessagesViewController.podspec; sourceTree = "<group>"; };
886FFD2C19E9A65D00EB8485 /* UIDevice+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIDevice+JSQMessages.h"; sourceTree = "<group>"; };
886FFD2D19E9A65D00EB8485 /* UIDevice+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+JSQMessages.m"; sourceTree = "<group>"; };
8873B60A1AB7B244006DF9AC /* NSBundle+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+JSQMessages.h"; sourceTree = "<group>"; };
8873B60B1AB7B244006DF9AC /* NSBundle+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+JSQMessages.m"; sourceTree = "<group>"; };
8873B60D1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesNSBundleTests.m; sourceTree = "<group>"; };
8885734819DE540400E89D20 /* DemoSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoSettingsViewController.h; sourceTree = "<group>"; };
8885734919DE540400E89D20 /* DemoSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoSettingsViewController.m; sourceTree = "<group>"; };
8885734B19DE55D000E89D20 /* NSUserDefaults+DemoSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUserDefaults+DemoSettings.h"; sourceTree = "<group>"; };
@ -364,12 +353,12 @@
636A8663AEEE5C37B65C515D /* Frameworks */ = {
isa = PBXGroup;
children = (
88445B4319E1B5110014F889 /* MapKit.framework */,
88445B4119E1B50B0014F889 /* CoreLocation.framework */,
88445B3419E0AE4A0014F889 /* CoreGraphics.framework */,
88445B4119E1B50B0014F889 /* CoreLocation.framework */,
88445B3219E0AE450014F889 /* Foundation.framework */,
782026E9E518622532ED474D /* libPods-JSQMessagesTests.a */,
97E6750B77E8A7042BA0754B /* libPods.a */,
88445B4319E1B5110014F889 /* MapKit.framework */,
88445B3619E0AE5C0014F889 /* QuartzCore.framework */,
88445B3019E0AE3F0014F889 /* UIKit.framework */,
88445B3A19E0C0B10014F889 /* XCTest.framework */,
@ -378,14 +367,6 @@
path = ../..;
sourceTree = "<group>";
};
8841B88219F4983C00EA16B6 /* Strings */ = {
isa = PBXGroup;
children = (
8841B88719F4988800EA16B6 /* JSQMessages.strings */,
);
path = Strings;
sourceTree = "<group>";
};
88A25EF919D8DEC400924534 = {
isa = PBXGroup;
children = (
@ -483,6 +464,8 @@
children = (
88A25F5519D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.h */,
88A25F5619D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.m */,
8873B60A1AB7B244006DF9AC /* NSBundle+JSQMessages.h */,
8873B60B1AB7B244006DF9AC /* NSBundle+JSQMessages.m */,
88A25F5719D8E01A00924534 /* NSString+JSQMessages.h */,
88A25F5819D8E01A00924534 /* NSString+JSQMessages.m */,
88A25F5919D8E01A00924534 /* UIColor+JSQMessages.h */,
@ -620,6 +603,7 @@
88A25FE219D8E18400924534 /* CategoryTests */ = {
isa = PBXGroup;
children = (
8873B60D1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m */,
88A25FE319D8E18400924534 /* JSQMessagesNSStringTests.m */,
88A25FE419D8E18400924534 /* JSQMessagesUIColorTests.m */,
88A25FE519D8E18400924534 /* JSQMessagesUIImageTests.m */,
@ -805,7 +789,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
88E4D7131A0DBD6B000CC061 /* JSQMessages.strings in Resources */,
886C33FF19F45E30006B4997 /* JSQMessagesViewController.podspec in Resources */,
8861666D19F492B70025B958 /* JSQMessagesAssets.bundle in Resources */,
FC15B7B11A1F6AC800F59801 /* JSQMessagesCollectionViewCellOutgoing.xib in Resources */,
@ -952,6 +935,7 @@
88A25FE119D8E0C400924534 /* TableViewController.m in Sources */,
88A25FBD19D8E01A00924534 /* JSQMessagesAvatarImageFactory.m in Sources */,
88A25FB519D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.m in Sources */,
8873B60C1AB7B244006DF9AC /* NSBundle+JSQMessages.m in Sources */,
88A25FD019D8E01A00924534 /* JSQMessagesComposerTextView.m in Sources */,
88A25FC319D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.m in Sources */,
);
@ -985,6 +969,7 @@
88A2601919D8E18400924534 /* JSQMessagesTypingIndicatorFooterViewTests.m in Sources */,
88A2600319D8E18400924534 /* JSQMessagesUIImageTests.m in Sources */,
88C00A4E1A44D4C600B004B3 /* JSQLocationMediaItemTests.m in Sources */,
8873B60E1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m in Sources */,
88A2600C19D8E18400924534 /* JSQMessagesCollectionViewLayoutAttributesTests.m in Sources */,
88A2600619D8E18400924534 /* JSQMessagesViewControllerTests.m in Sources */,
88A2600519D8E18400924534 /* JSQMessagesKeyboardControllerTests.m in Sources */,
@ -1003,28 +988,6 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
8841B88719F4988800EA16B6 /* JSQMessages.strings */ = {
isa = PBXVariantGroup;
children = (
8841B88619F4988800EA16B6 /* Base */,
8841B88819F4988900EA16B6 /* en */,
8841B88919F4988A00EA16B6 /* es */,
8841B88A19F4988B00EA16B6 /* de */,
8841B88B19F4988C00EA16B6 /* zh-Hans */,
8841B88C19F4988F00EA16B6 /* zh-Hant */,
8841B88D19F4989000EA16B6 /* ro */,
8841B88E19F4989100EA16B6 /* pl */,
8841B88F19F4989200EA16B6 /* ru */,
8841B89019F4989200EA16B6 /* pt */,
8841B89119F4989300EA16B6 /* fr */,
8841B89219F4989400EA16B6 /* it */,
8841B89319F4989500EA16B6 /* he */,
8841B89419F4989500EA16B6 /* nl */,
8841B89519F4989600EA16B6 /* tr */,
);
name = JSQMessages.strings;
sourceTree = "<group>";
};
88A25F3019D8DF2500924534 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="13F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="JRd-Be-psV">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7531" systemVersion="14D131" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="JRd-Be-psV">
<dependencies>
<deployment defaultVersion="1792" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7520"/>
</dependencies>
<scenes>
<!--Root View Controller-->
@ -80,10 +80,9 @@
<constraints>
<constraint firstItem="bSS-CD-nfD" firstAttribute="top" secondItem="qU9-o3-MWC" secondAttribute="top" constant="10" id="4Iw-W0-qZs"/>
<constraint firstAttribute="trailing" secondItem="K7N-os-fuc" secondAttribute="trailing" constant="8" id="Fex-nr-C1R"/>
<constraint firstAttribute="bottom" secondItem="K7N-os-fuc" secondAttribute="bottom" constant="6" id="IBG-gn-Fpk"/>
<constraint firstItem="bSS-CD-nfD" firstAttribute="leading" secondItem="qU9-o3-MWC" secondAttribute="leading" constant="8" id="N5B-E5-Pzk"/>
<constraint firstItem="K7N-os-fuc" firstAttribute="leading" secondItem="bSS-CD-nfD" secondAttribute="trailing" constant="8" id="O2Y-G9-d6n"/>
<constraint firstItem="K7N-os-fuc" firstAttribute="top" secondItem="qU9-o3-MWC" secondAttribute="top" constant="6" id="UbM-aE-caS"/>
<constraint firstAttribute="centerY" secondItem="K7N-os-fuc" secondAttribute="centerY" id="j7N-59-vRk"/>
<constraint firstAttribute="bottom" secondItem="bSS-CD-nfD" secondAttribute="bottom" constant="9" id="poL-wE-Eir"/>
</constraints>
</tableViewCellContentView>
@ -111,11 +110,10 @@
</label>
</subviews>
<constraints>
<constraint firstAttribute="centerY" secondItem="hMq-Ee-EJK" secondAttribute="centerY" id="CcO-KD-31y"/>
<constraint firstItem="hMq-Ee-EJK" firstAttribute="leading" secondItem="YV3-GH-Yul" secondAttribute="trailing" constant="8" id="KFO-nO-4r4"/>
<constraint firstItem="YV3-GH-Yul" firstAttribute="top" secondItem="1Ho-Zz-KN0" secondAttribute="top" constant="10" id="Lgl-fY-a8c"/>
<constraint firstAttribute="bottom" secondItem="YV3-GH-Yul" secondAttribute="bottom" constant="9" id="NvL-b8-QDl"/>
<constraint firstAttribute="bottom" secondItem="hMq-Ee-EJK" secondAttribute="bottom" constant="6" id="aef-Rh-49d"/>
<constraint firstItem="hMq-Ee-EJK" firstAttribute="top" secondItem="1Ho-Zz-KN0" secondAttribute="top" constant="6" id="csX-us-yCd"/>
<constraint firstAttribute="trailing" secondItem="hMq-Ee-EJK" secondAttribute="trailing" constant="8" id="xDE-tf-5mB"/>
<constraint firstItem="YV3-GH-Yul" firstAttribute="leading" secondItem="1Ho-Zz-KN0" secondAttribute="leading" constant="8" id="xyR-oc-iUp"/>
</constraints>
@ -145,12 +143,11 @@
</subviews>
<constraints>
<constraint firstItem="DoU-SU-Nek" firstAttribute="leading" secondItem="OFq-Mz-mbl" secondAttribute="leading" constant="8" id="A9k-hI-0BH"/>
<constraint firstAttribute="bottom" secondItem="baL-Tb-bTJ" secondAttribute="bottom" constant="6" id="Ff7-1T-EbZ"/>
<constraint firstItem="DoU-SU-Nek" firstAttribute="top" secondItem="OFq-Mz-mbl" secondAttribute="top" constant="10" id="Uji-LE-8IA"/>
<constraint firstAttribute="trailing" secondItem="baL-Tb-bTJ" secondAttribute="trailing" constant="8" id="dUp-7b-g3p"/>
<constraint firstItem="baL-Tb-bTJ" firstAttribute="leading" secondItem="DoU-SU-Nek" secondAttribute="trailing" constant="8" id="fxZ-zy-ksL"/>
<constraint firstAttribute="bottom" secondItem="DoU-SU-Nek" secondAttribute="bottom" constant="9" id="h0B-EX-3MW"/>
<constraint firstItem="baL-Tb-bTJ" firstAttribute="top" secondItem="OFq-Mz-mbl" secondAttribute="top" constant="6" id="v5e-xk-srk"/>
<constraint firstAttribute="centerY" secondItem="baL-Tb-bTJ" secondAttribute="centerY" id="hx4-cn-W0h"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
@ -181,13 +178,12 @@
</label>
</subviews>
<constraints>
<constraint firstAttribute="centerY" secondItem="fLZ-NC-aPO" secondAttribute="centerY" id="17e-cC-bP4"/>
<constraint firstAttribute="bottom" secondItem="RUq-Pa-3nx" secondAttribute="bottom" constant="9" id="7Yt-uN-sOS"/>
<constraint firstItem="RUq-Pa-3nx" firstAttribute="top" secondItem="dk1-tc-gux" secondAttribute="top" constant="10" id="Aih-Lc-tq7"/>
<constraint firstItem="fLZ-NC-aPO" firstAttribute="leading" secondItem="RUq-Pa-3nx" secondAttribute="trailing" constant="8" id="Cfo-7m-Vyd"/>
<constraint firstAttribute="trailing" secondItem="fLZ-NC-aPO" secondAttribute="trailing" constant="8" id="Mgz-VG-tDn"/>
<constraint firstItem="fLZ-NC-aPO" firstAttribute="top" secondItem="dk1-tc-gux" secondAttribute="top" constant="6" id="S6i-he-vB9"/>
<constraint firstItem="RUq-Pa-3nx" firstAttribute="leading" secondItem="dk1-tc-gux" secondAttribute="leading" constant="8" id="mhd-oX-p53"/>
<constraint firstAttribute="bottom" secondItem="fLZ-NC-aPO" secondAttribute="bottom" constant="6" id="yyq-Sg-nJi"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
@ -214,11 +210,10 @@
</label>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="LuM-mk-Zj6" secondAttribute="bottom" constant="6" id="1G7-8S-rYC"/>
<constraint firstAttribute="centerY" secondItem="LuM-mk-Zj6" secondAttribute="centerY" id="2oC-JM-LVI"/>
<constraint firstAttribute="bottom" secondItem="9Rr-S8-Uae" secondAttribute="bottom" constant="9" id="5xp-ez-tac"/>
<constraint firstItem="LuM-mk-Zj6" firstAttribute="leading" secondItem="9Rr-S8-Uae" secondAttribute="trailing" constant="8" id="C5c-2A-mli"/>
<constraint firstAttribute="trailing" secondItem="LuM-mk-Zj6" secondAttribute="trailing" constant="8" id="Lep-LY-D1h"/>
<constraint firstItem="LuM-mk-Zj6" firstAttribute="top" secondItem="zad-JQ-TRI" secondAttribute="top" constant="6" id="Y6a-JS-aLj"/>
<constraint firstItem="9Rr-S8-Uae" firstAttribute="leading" secondItem="zad-JQ-TRI" secondAttribute="leading" constant="8" id="Yao-VR-Is6"/>
<constraint firstItem="9Rr-S8-Uae" firstAttribute="top" secondItem="zad-JQ-TRI" secondAttribute="top" constant="10" id="Yiv-t6-Xj5"/>
</constraints>
@ -253,10 +248,9 @@
<constraints>
<constraint firstItem="3d2-fZ-dx9" firstAttribute="leading" secondItem="btE-Mk-fSE" secondAttribute="leading" constant="8" id="6Cs-6l-Xzp"/>
<constraint firstItem="uXC-2j-cgi" firstAttribute="leading" secondItem="3d2-fZ-dx9" secondAttribute="trailing" constant="8" id="7Mq-HR-4nY"/>
<constraint firstItem="uXC-2j-cgi" firstAttribute="top" secondItem="btE-Mk-fSE" secondAttribute="top" constant="6" id="Ajq-5g-XKo"/>
<constraint firstAttribute="bottom" secondItem="3d2-fZ-dx9" secondAttribute="bottom" constant="9" id="EzH-aB-ePQ"/>
<constraint firstAttribute="bottom" secondItem="uXC-2j-cgi" secondAttribute="bottom" constant="6" id="RCb-Mi-FHX"/>
<constraint firstItem="3d2-fZ-dx9" firstAttribute="top" secondItem="btE-Mk-fSE" secondAttribute="top" constant="10" id="btF-q4-HJg"/>
<constraint firstAttribute="centerY" secondItem="uXC-2j-cgi" secondAttribute="centerY" id="zJf-Xj-C1X"/>
<constraint firstAttribute="trailing" secondItem="uXC-2j-cgi" secondAttribute="trailing" constant="8" id="zzu-aP-ZiL"/>
</constraints>
</tableViewCellContentView>

View File

@ -74,13 +74,26 @@
style:UIBarButtonItemStyleBordered
target:self
action:@selector(receiveMessagePressed:)];
/**
* Register custom menu actions for cells.
*/
[JSQMessagesCollectionViewCell registerMenuAction:@selector(customAction:)];
[UIMenuController sharedMenuController].menuItems = @[ [[UIMenuItem alloc] initWithTitle:@"Custom Action" action:@selector(customAction:)] ];
/**
* Customize your toolbar buttons
*
* self.inputToolbar.contentView.leftBarButtonItem = custom button or nil to remove
* self.inputToolbar.contentView.rightBarButtonItem = custom button or nil to remove
*/
/**
* Set a maximum height for the input toolbar
*
* self.inputToolbar.maximumHeight = 150;
*/
}
- (void)viewWillAppear:(BOOL)animated
@ -507,6 +520,43 @@
#pragma mark - UICollectionView Delegate
#pragma mark - Custom menu items
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(customAction:)) {
return YES;
}
return [super collectionView:collectionView canPerformAction:action forItemAtIndexPath:indexPath withSender:sender];
}
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(customAction:)) {
[self customAction:sender];
return;
}
[super collectionView:collectionView performAction:action forItemAtIndexPath:indexPath withSender:sender];
}
- (void)customAction:(id)sender
{
NSLog(@"Custom action received! Sender: %@", sender);
[[[UIAlertView alloc] initWithTitle:@"Custom Action"
message:nil
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil]
show];
}
#pragma mark - JSQMessages collection view flow layout delegate
#pragma mark - Adjusting cell label heights

View File

@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>6.1.1</string>
<string>7.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>6.1.1</string>
<string>7.1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>

View File

@ -0,0 +1,46 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// MIT License
// Copyright (c) 2014 Jesse Squires
// http://opensource.org/licenses/MIT
//
#import <XCTest/XCTest.h>
#import "NSBundle+JSQMessages.h"
@interface JSQMessagesNSBundleTests : XCTestCase
@end
@implementation JSQMessagesNSBundleTests
- (void)testMessagesBundle
{
XCTAssertNotNil([NSBundle jsq_messagesBundle]);
}
- (void)testAssetBundle
{
NSBundle *bundle = [NSBundle jsq_messagesAssetBundle];
XCTAssertNotNil(bundle);
XCTAssertEqualObjects(bundle.bundlePath.lastPathComponent, @"JSQMessagesAssets.bundle");
}
- (void)testLocalizedStringForKey
{
XCTAssertNotNil([NSBundle jsq_localizedStringForKey:@"send"]);
XCTAssertNotEqualObjects([NSBundle jsq_localizedStringForKey:@"send"], @"send");
XCTAssertNotNil([NSBundle jsq_localizedStringForKey:@"load_earlier_messages"]);
XCTAssertNotEqualObjects([NSBundle jsq_localizedStringForKey:@"load_earlier_messages"], @"load_earlier_messages");
XCTAssertNotNil([NSBundle jsq_localizedStringForKey:@"new_message"]);
XCTAssertNotEqualObjects([NSBundle jsq_localizedStringForKey:@"new_message"], @"new_message");
}
@end

View File

@ -53,9 +53,7 @@
XCTAssertNotNil(vc.view, @"View should not be nil");
XCTAssertNotNil(vc.collectionView, @"Collection view should not be nil");
XCTAssertNotNil(vc.inputToolbar, @"Input toolbar should not be nil");
XCTAssertEqualObjects(vc.senderId, @"JSQDefaultSender", @"Property should be equal to default value");
XCTAssertEqualObjects(vc.senderDisplayName, @"JSQDefaultSender", @"Property should be equal to default value");
XCTAssertEqual(vc.automaticallyAdjustsScrollViewInsets, YES, @"Property should be equal to default value");
XCTAssertEqualObjects(vc.incomingCellIdentifier, [JSQMessagesCollectionViewCellIncoming cellReuseIdentifier], @"Property should be equal to default value");

View File

@ -30,6 +30,10 @@
- (BOOL)isEqual:(id)object { return YES; }
- (NSUInteger)hash { return 10000; }
- (NSUInteger)mediaHash { return self.hash; }
@end
@ -40,7 +44,7 @@
@property (strong, nonatomic) NSString *senderId;
@property (strong, nonatomic) NSString *senderDisplayName;
@property (strong, nonatomic) NSDate *date;
@property (strong, nonatomic) id<JSQMessageMediaData> mockMediaData;
@property (strong, nonatomic) id mockMediaData;
@end
@ -53,7 +57,9 @@
self.senderId = @"324543-43556-212343";
self.senderDisplayName = @"Jesse Squires";
self.date = [NSDate date];
self.mockMediaData = [OCMockObject mockForProtocol:@protocol(JSQMessageMediaData)];
[[self.mockMediaData stub] mediaHash];
}
- (void)tearDown

View File

@ -39,7 +39,7 @@
XCTAssertNotNil(view, @"Collection view should not be nil");
XCTAssertEqualObjects(view.backgroundColor, [UIColor whiteColor], @"Property should be equal to default value");
XCTAssertEqual(view.keyboardDismissMode, UIScrollViewKeyboardDismissModeInteractive, @"Property should be equal to default value");
XCTAssertEqual(view.keyboardDismissMode, UIScrollViewKeyboardDismissModeNone, @"Property should be equal to default value");
XCTAssertEqual(view.alwaysBounceVertical, YES, @"Property should be equal to default value");
XCTAssertEqual(view.bounces, YES, @"Property should be equal to default value");
}

View File

@ -12,6 +12,7 @@
#import "JSQMessagesViewController.h"
#import "JSQMessagesInputToolbar.h"
#import "DemoMessagesViewController.h"
@interface JSQMessagesInputToolbarTests : XCTestCase
@ -34,11 +35,32 @@
{
JSQMessagesViewController *vc = [JSQMessagesViewController messagesViewController];
[vc loadView];
JSQMessagesInputToolbar *toolbar = vc.inputToolbar;
XCTAssertNotNil(toolbar, @"Toolbar should not be nil");
XCTAssertNotNil(toolbar.contentView, @"Toolbar content view should not be nil");
XCTAssertEqual(toolbar.sendButtonOnRight, YES, @"Property should be equal to default value");
}
- (void)testSetMaximumHeight
{
UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
XCTAssertNotNil(mainSB, @"Storyboard should not be nil");
DemoMessagesViewController *demoVC = [mainSB instantiateViewControllerWithIdentifier:@"DemoVC"];
[demoVC view];
XCTAssertEqual(demoVC.inputToolbar.maximumHeight, NSNotFound, @"maximumInputToolbarHeight should equal default value");
CGRect newBounds = demoVC.inputToolbar.bounds;
newBounds.size.height = 100;
demoVC.inputToolbar.bounds = newBounds;
XCTAssertEqual(demoVC.inputToolbar.bounds.size.height, 100);
demoVC.inputToolbar.maximumHeight = 54;
[demoVC viewDidLoad];
XCTAssertLessThanOrEqual(CGRectGetHeight(demoVC.inputToolbar.frame), 54, @"Toolbar height should be <= to maximumInputToolbarHeight");
}
@end

View File

@ -1,21 +1,26 @@
Pod::Spec.new do |s|
s.name = 'JSQMessagesViewController'
s.version = '6.1.1'
s.summary = 'An elegant messages UI library for iOS.'
s.homepage = 'http://jessesquires.github.io/JSQMessagesViewController'
s.license = 'MIT'
s.screenshots = ['https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot0.png',
'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot1.png',
'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot2.png',
'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot3.png']
s.author = { 'Jesse Squires' => 'jesse.squires.developer@gmail.com' }
s.social_media_url = 'https://twitter.com/jesse_squires'
s.source = { :git => 'https://github.com/jessesquires/JSQMessagesViewController.git', :tag => s.version.to_s }
s.platform = :ios, '7.0'
s.source_files = 'JSQMessagesViewController/**/*.{h,m}'
s.resources = 'JSQMessagesViewController/Assets/JSQMessagesAssets.bundle', 'JSQMessagesViewController/Assets/Strings/*.lproj', 'JSQMessagesViewController/**/*.{xib}',
s.frameworks = 'QuartzCore', 'CoreGraphics', 'CoreLocation', 'MapKit', 'UIKit', 'Foundation'
s.requires_arc = true
s.name = 'JSQMessagesViewController'
s.version = '7.1.0'
s.summary = 'An elegant messages UI library for iOS.'
s.homepage = 'http://jessesquires.github.io/JSQMessagesViewController'
s.license = 'MIT'
s.platform = :ios, '7.0'
s.dependency 'JSQSystemSoundPlayer', '~> 2.0.0'
s.author = { 'Jesse Squires' => 'jesse.squires.developer@gmail.com' }
s.social_media_url = 'https://twitter.com/jesse_squires'
s.screenshots = ['https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot0.png',
'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot1.png',
'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot2.png',
'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot3.png']
s.source = { :git => 'https://github.com/jessesquires/JSQMessagesViewController.git', :tag => s.version }
s.source_files = 'JSQMessagesViewController/**/*.{h,m}'
s.resources = ['JSQMessagesViewController/Assets/JSQMessagesAssets.bundle', 'JSQMessagesViewController/**/*.{xib}']
s.frameworks = 'QuartzCore', 'CoreGraphics', 'CoreLocation', 'MapKit', 'UIKit', 'Foundation'
s.requires_arc = true
s.dependency 'JSQSystemSoundPlayer', '~> 2.0.1'
end

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Load Earlier Messages";
"load_earlier_messages" = "Load Earlier Messages";
"Send" = "Send";
"send" = "Send";
"New Message" = "New Message";
"new_message" = "New Message";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Ältere Nachrichten laden";
"load_earlier_messages" = "Ältere Nachrichten laden";
"Send" = "Senden";
"send" = "Senden";
"New Message" = "Neue Nachricht";
"new_message" = "Neue Nachricht";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "載入之前的訊息";
"load_earlier_messages" = "Load Earlier Messages";
"Send" = "傳送";
"send" = "Send";
"New Message" = "新信息";
"new_message" = "New Message";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Cargar mensajes anteriores";
"load_earlier_messages" = "Cargar mensajes anteriores";
"Send" = "Enviar";
"send" = "Enviar";
"New Message" = "Nuevo mensaje";
"new_message" = "Nuevo mensaje";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Messages précedents";
"load_earlier_messages" = "Messages précedents";
"Send" = "Envoi";
"send" = "Envoi";
"New Message" = "Nouveau message";
"new_message" = "Nouveau message";

View File

@ -0,0 +1,29 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"load_earlier_messages" = "טען הודעות קודמות";
"send" = "שלח";
"new_message" = "הודעה חדשה";

View File

@ -0,0 +1,29 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"load_earlier_messages" = "Carica messaggi precedenti";
"send" = "Invia";
"new_message" = "Nuovo Messaggio";

View File

@ -0,0 +1,29 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"load_earlier_messages" = "古いメッセージを読み込む";
"send" = "送信";
"new_message" = "新しいメッセージ";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Laad eerdere berichten";
"load_earlier_messages" = "Laad eerdere berichten";
"Send" = "Stuur";
"send" = "Stuur";
"New Message" = "Nieuw bericht";
"new_message" = "Nieuw bericht";

View File

@ -0,0 +1,29 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"load_earlier_messages" = "Otwórz wcześniejsze wiadomości";
"send" = "Wyślij";
"new_message" = "Nowa wiadomość";

View File

@ -0,0 +1,29 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"load_earlier_messages" = "Carregar mensagens anteriore";
"send" = "Enviar";
"new_message" = "Nova Mensagem";

View File

@ -0,0 +1,29 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"load_earlier_messages" = "Încărcați mesajele anterioare";
"send" = "Trimiteți";
"new_message" = "Mesaj nou";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Предыдущие сообщения";
"load_earlier_messages" = "Предыдущие сообщения";
"Send" = "Отпр";
"send" = "Отпр";
"New Message" = "Сообщение";
"new_message" = "Сообщение";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Eski mesajları yükle";
"load_earlier_messages" = "Eski mesajları yükle";
"Send" = "Gönder";
"send" = "Gönder";
"New Message" = "Yeni Mesaj";
"new_message" = "Yeni Mesaj";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Load Earlier Messages";
"load_earlier_messages" = "载入较早的信息";
"Send" = "Send";
"send" = "发送";
"New Message" = "New Message";
"new_message" = "新信息";

View File

@ -22,8 +22,8 @@
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "载入较早的信息";
"load_earlier_messages" = "載入之前的訊息";
"Send" = "发送";
"send" = "傳送";
"New Message" = "新信息";
"new_message" = "新信息";

View File

@ -1,29 +0,0 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "טען הודעות קודמות";
"Send" = "שלח";
"New Message" = "הודעה חדשה";

View File

@ -1,29 +0,0 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Carica messaggi precedenti";
"Send" = "Invia";
"New Message" = "Nuovo Messaggio";

View File

@ -1,29 +0,0 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Otwórz wcześniejsze wiadomości";
"Send" = "Wyślij";
"New Message" = "Nowa wiadomość";

View File

@ -1,29 +0,0 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Carregar mensagens anteriore";
"Send" = "Enviar";
"New Message" = "Nova Mensagem";

View File

@ -1,29 +0,0 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
// ********************************
// Special thanks to the localization contributors!
//
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
// ********************************
"Load Earlier Messages" = "Încărcați mesajele anterioare";
"Send" = "Trimiteți";
"New Message" = "Mesaj nou";

View File

@ -18,34 +18,58 @@
#import "JSQSystemSoundPlayer+JSQMessages.h"
static NSString * const kJSQMessageReceivedSoundName = @"JSQMessagesAssets.bundle/Sounds/message_received";
static NSString * const kJSQMessageSentSoundName = @"JSQMessagesAssets.bundle/Sounds/message_sent";
#import "NSBundle+JSQMessages.h"
static NSString * const kJSQMessageReceivedSoundName = @"message_received";
static NSString * const kJSQMessageSentSoundName = @"message_sent";
@implementation JSQSystemSoundPlayer (JSQMessages)
#pragma mark - Public
+ (void)jsq_playMessageReceivedSound
{
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithFilename:kJSQMessageReceivedSoundName
fileExtension:kJSQSystemSoundTypeAIFF];
[self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageReceivedSoundName asAlert:NO];
}
+ (void)jsq_playMessageReceivedAlert
{
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithFilename:kJSQMessageReceivedSoundName
fileExtension:kJSQSystemSoundTypeAIFF];
[self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageReceivedSoundName asAlert:YES];
}
+ (void)jsq_playMessageSentSound
{
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithFilename:kJSQMessageSentSoundName
fileExtension:kJSQSystemSoundTypeAIFF];
[self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageSentSoundName asAlert:NO];
}
+ (void)jsq_playMessageSentAlert
{
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithFilename:kJSQMessageSentSoundName
fileExtension:kJSQSystemSoundTypeAIFF];
[self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageSentSoundName asAlert:YES];
}
#pragma mark - Private
+ (void)jsq_playSoundFromJSQMessagesBundleWithName:(NSString *)soundName asAlert:(BOOL)asAlert
{
// save sound player original bundle
NSString *originalPlayerBundleIdentifier = [JSQSystemSoundPlayer sharedPlayer].bundle.bundleIdentifier;
// search for sounds in this library's bundle
[JSQSystemSoundPlayer sharedPlayer].bundle = [NSBundle jsq_messagesBundle];
NSString *fileName = [NSString stringWithFormat:@"JSQMessagesAssets.bundle/Sounds/%@", soundName];
if (asAlert) {
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithFilename:fileName fileExtension:kJSQSystemSoundTypeAIFF];
}
else {
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithFilename:fileName fileExtension:kJSQSystemSoundTypeAIFF];
}
// restore original bundle
[JSQSystemSoundPlayer sharedPlayer].bundle = [NSBundle bundleWithIdentifier:originalPlayerBundleIdentifier];
}
@end

View File

@ -0,0 +1,42 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
#import <Foundation/Foundation.h>
@interface NSBundle (JSQMessages)
/**
* @return The bundle for JSQMessagesViewController.
*/
+ (NSBundle *)jsq_messagesBundle;
/**
* @return The bundle for assets in JSQMessagesViewController.
*/
+ (NSBundle *)jsq_messagesAssetBundle;
/**
* Returns a localized version of the string designated by the specified key and residing in the JSQMessages table.
*
* @param key The key for a string in the JSQMessages table.
*
* @return A localized version of the string designated by key in the JSQMessages table.
*/
+ (NSString *)jsq_localizedStringForKey:(NSString *)key;
@end

View File

@ -0,0 +1,42 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
// GitHub
// https://github.com/jessesquires/JSQMessagesViewController
//
//
// License
// Copyright (c) 2014 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
#import "NSBundle+JSQMessages.h"
#import "JSQMessagesViewController.h"
@implementation NSBundle (JSQMessages)
+ (NSBundle *)jsq_messagesBundle
{
return [NSBundle bundleForClass:[JSQMessagesViewController class]];
}
+ (NSBundle *)jsq_messagesAssetBundle
{
NSString *bundleResourcePath = [NSBundle jsq_messagesBundle].resourcePath;
NSString *assetPath = [bundleResourcePath stringByAppendingPathComponent:@"JSQMessagesAssets.bundle"];
return [NSBundle bundleWithPath:assetPath];
}
+ (NSString *)jsq_localizedStringForKey:(NSString *)key
{
return NSLocalizedStringFromTableInBundle(key, @"JSQMessages", [NSBundle jsq_messagesAssetBundle], nil);
}
@end

View File

@ -23,7 +23,7 @@
+ (BOOL)jsq_isCurrentDeviceBeforeiOS8
{
// iOS < 8.0
return [[UIDevice currentDevice].systemVersion compare:@"8.0.0" options:NSNumericSearch] == NSOrderedAscending;
return [[UIDevice currentDevice].systemVersion compare:@"8.0" options:NSNumericSearch] == NSOrderedAscending;
}
@end

View File

@ -18,6 +18,9 @@
#import "UIImage+JSQMessages.h"
#import "NSBundle+JSQMessages.h"
@implementation UIImage (JSQMessages)
- (UIImage *)jsq_imageMaskedWithColor:(UIColor *)maskColor
@ -47,7 +50,9 @@
+ (UIImage *)jsq_bubbleImageFromBundleWithName:(NSString *)name
{
return [UIImage imageNamed:[NSString stringWithFormat:@"JSQMessagesAssets.bundle/Images/%@", name]];
NSBundle *bundle = [NSBundle jsq_messagesAssetBundle];
NSString *path = [bundle pathForResource:name ofType:@"png" inDirectory:@"Images"];
return [UIImage imageWithContentsOfFile:path];
}
+ (UIImage *)jsq_bubbleRegularImage

View File

@ -59,13 +59,6 @@ FOUNDATION_EXPORT NSString * const JSQMessagesKeyboardControllerUserInfoKeyKeybo
*/
- (void)keyboardController:(JSQMessagesKeyboardController *)keyboardController keyboardDidChangeFrame:(CGRect)keyboardFrame;
/**
* Tells the delegate that the keyboard has been hidden.
*
* @param keyboardController The keyboard controller that is notifying the delegate.
*/
- (void)keyboardControllerKeyboardDidHide:(JSQMessagesKeyboardController *)keyboardController;
@end

View File

@ -51,8 +51,8 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
- (void)jsq_handleKeyboardNotification:(NSNotification *)notification completion:(JSQAnimationCompletionBlock)completion;
- (void)jsq_setKeyboardViewHidden:(BOOL)hidden;
- (void)jsq_notifyKeyboardFrameNotificationForFrame:(CGRect)frame;
- (void)jsq_resetKeyboardAndTextView;
- (void)jsq_removeKeyboardFrameObserver;
@ -75,7 +75,7 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
NSParameterAssert(textView != nil);
NSParameterAssert(contextView != nil);
NSParameterAssert(panGestureRecognizer != nil);
self = [super init];
if (self) {
_textView = textView;
@ -105,15 +105,15 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
if (_keyboardView) {
[self jsq_removeKeyboardFrameObserver];
}
_keyboardView = keyboardView;
if (keyboardView && !_jsq_isObserving) {
[_keyboardView addObserver:self
forKeyPath:NSStringFromSelector(@selector(frame))
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:kJSQMessagesKeyboardControllerKeyValueObservingContext];
_jsq_isObserving = YES;
}
}
@ -130,7 +130,7 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
if (!self.keyboardIsVisible) {
return CGRectNull;
}
return self.keyboardView.frame;
}
@ -141,14 +141,14 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
if (self.textView.inputAccessoryView == nil) {
self.textView.inputAccessoryView = [[UIView alloc] init];
}
[self jsq_registerForNotifications];
}
- (void)endListeningForKeyboard
{
[self jsq_unregisterForNotifications];
[self jsq_setKeyboardViewHidden:NO];
self.keyboardView = nil;
}
@ -158,22 +158,22 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
- (void)jsq_registerForNotifications
{
[self jsq_unregisterForNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveKeyboardDidShowNotification:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveKeyboardWillChangeFrameNotification:)
name:UIKeyboardWillChangeFrameNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveKeyboardDidChangeFrameNotification:)
name:UIKeyboardDidChangeFrameNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveKeyboardDidHideNotification:)
name:UIKeyboardDidHideNotification
@ -189,7 +189,7 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
{
self.keyboardView = self.textView.inputAccessoryView.superview;
[self jsq_setKeyboardViewHidden:NO];
[self jsq_handleKeyboardNotification:notification completion:^(BOOL finished) {
[self.panGestureRecognizer addTarget:self action:@selector(jsq_handlePanGestureRecognizer:)];
}];
@ -203,38 +203,36 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
- (void)jsq_didReceiveKeyboardDidChangeFrameNotification:(NSNotification *)notification
{
[self jsq_setKeyboardViewHidden:NO];
[self jsq_handleKeyboardNotification:notification completion:nil];
}
- (void)jsq_didReceiveKeyboardDidHideNotification:(NSNotification *)notification
{
self.keyboardView = nil;
[self jsq_handleKeyboardNotification:notification completion:^(BOOL finished) {
[self.panGestureRecognizer removeTarget:self action:NULL];
[self.delegate keyboardControllerKeyboardDidHide:self];
}];
}
- (void)jsq_handleKeyboardNotification:(NSNotification *)notification completion:(JSQAnimationCompletionBlock)completion
{
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardEndFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
if (CGRectIsNull(keyboardEndFrame)) {
return;
}
UIViewAnimationCurve animationCurve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
NSInteger animationCurveOption = (animationCurve << 16);
double animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardEndFrameConverted = [self.contextView convertRect:keyboardEndFrame fromView:nil];
[UIView animateWithDuration:animationDuration
delay:0.0
options:animationCurveOption
@ -259,31 +257,37 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
- (void)jsq_notifyKeyboardFrameNotificationForFrame:(CGRect)frame
{
[self.delegate keyboardController:self keyboardDidChangeFrame:frame];
[[NSNotificationCenter defaultCenter] postNotificationName:JSQMessagesKeyboardControllerNotificationKeyboardDidChangeFrame
object:self
userInfo:@{ JSQMessagesKeyboardControllerUserInfoKeyKeyboardDidChangeFrame : [NSValue valueWithCGRect:frame] }];
}
- (void)jsq_resetKeyboardAndTextView
{
[self jsq_setKeyboardViewHidden:YES];
[self jsq_removeKeyboardFrameObserver];
[self.textView resignFirstResponder];
}
#pragma mark - Key-value observing
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == kJSQMessagesKeyboardControllerKeyValueObservingContext) {
if (object == self.keyboardView && [keyPath isEqualToString:NSStringFromSelector(@selector(frame))]) {
CGRect oldKeyboardFrame = [[change objectForKey:NSKeyValueChangeOldKey] CGRectValue];
CGRect newKeyboardFrame = [[change objectForKey:NSKeyValueChangeNewKey] CGRectValue];
if (CGRectEqualToRect(newKeyboardFrame, oldKeyboardFrame) || CGRectIsNull(newKeyboardFrame)) {
return;
}
// do not convert frame to contextView coordinates here
// KVO is triggered during panning (see below)
// panning occurs in contextView coordinates already
[self jsq_notifyKeyboardFrameNotificationForFrame:newKeyboardFrame];
CGRect keyboardEndFrameConverted = [self.contextView convertRect:newKeyboardFrame
fromView:self.keyboardView.superview];
[self jsq_notifyKeyboardFrameNotificationForFrame:keyboardEndFrameConverted];
}
}
}
@ -293,14 +297,14 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
if (!_jsq_isObserving) {
return;
}
@try {
[_keyboardView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(frame))
context:kJSQMessagesKeyboardControllerKeyValueObservingContext];
}
@catch (NSException * __unused exception) { }
_jsq_isObserving = NO;
}
@ -309,41 +313,41 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
- (void)jsq_handlePanGestureRecognizer:(UIPanGestureRecognizer *)pan
{
CGPoint touch = [pan locationInView:self.contextView];
// system keyboard is added to a new UIWindow, need to operate in window coordinates
// also, keyboard always slides from bottom of screen, not the bottom of a view
CGFloat contextViewWindowHeight = CGRectGetHeight(self.contextView.window.frame);
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
// handle iOS 7 bug when rotating to landscape
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
contextViewWindowHeight = CGRectGetWidth(self.contextView.window.frame);
}
}
CGFloat keyboardViewHeight = CGRectGetHeight(self.keyboardView.frame);
CGFloat dragThresholdY = (contextViewWindowHeight - keyboardViewHeight - self.keyboardTriggerPoint.y);
CGRect newKeyboardViewFrame = self.keyboardView.frame;
BOOL userIsDraggingNearThresholdForDismissing = (touch.y > dragThresholdY);
self.keyboardView.userInteractionEnabled = !userIsDraggingNearThresholdForDismissing;
switch (pan.state) {
case UIGestureRecognizerStateChanged:
{
newKeyboardViewFrame.origin.y = touch.y + self.keyboardTriggerPoint.y;
// bound frame between bottom of view and height of keyboard
newKeyboardViewFrame.origin.y = MIN(newKeyboardViewFrame.origin.y, contextViewWindowHeight);
newKeyboardViewFrame.origin.y = MAX(newKeyboardViewFrame.origin.y, contextViewWindowHeight - keyboardViewHeight);
if (CGRectGetMinY(newKeyboardViewFrame) == CGRectGetMinY(self.keyboardView.frame)) {
return;
}
[UIView animateWithDuration:0.0
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionTransitionNone
@ -353,22 +357,23 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
completion:nil];
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
{
BOOL keyboardViewIsHidden = (CGRectGetMinY(self.keyboardView.frame) >= contextViewWindowHeight);
if (keyboardViewIsHidden) {
[self jsq_resetKeyboardAndTextView];
return;
}
CGPoint velocity = [pan velocityInView:self.contextView];
BOOL userIsScrollingDown = (velocity.y > 0.0f);
BOOL shouldHide = (userIsScrollingDown && userIsDraggingNearThresholdForDismissing);
newKeyboardViewFrame.origin.y = shouldHide ? contextViewWindowHeight : (contextViewWindowHeight - keyboardViewHeight);
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseOut
@ -377,16 +382,14 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
}
completion:^(BOOL finished) {
self.keyboardView.userInteractionEnabled = !shouldHide;
if (shouldHide) {
[self jsq_setKeyboardViewHidden:YES];
[self jsq_removeKeyboardFrameObserver];
[self.textView resignFirstResponder];
[self jsq_resetKeyboardAndTextView];
}
}];
}
break;
default:
break;
}

View File

@ -21,6 +21,7 @@
#import "JSQMessagesCollectionView.h"
#import "JSQMessagesCollectionViewFlowLayout.h"
#import "JSQMessagesInputToolbar.h"
#import "JSQMessagesKeyboardController.h"
/**
* The `JSQMessagesViewController` class is an abstract class that represents a view controller whose content consists of
@ -33,40 +34,43 @@
UITextViewDelegate>
/**
* Returns the collection view object managed by this view controller.
* Returns the collection view object managed by this view controller.
* This view controller is the collection view's data source and delegate.
*/
@property (weak, nonatomic, readonly) JSQMessagesCollectionView *collectionView;
/**
* Returns the input toolbar view object managed by this view controller.
* Returns the input toolbar view object managed by this view controller.
* This view controller is the toolbar's delegate.
*/
@property (weak, nonatomic, readonly) JSQMessagesInputToolbar *inputToolbar;
/**
* Returns the keyboard controller object used to manage the software keyboard.
*/
@property (strong, nonatomic) JSQMessagesKeyboardController *keyboardController;
/**
* The display name of the current user who is sending messages.
* This value does not have to be unique.
*
* @discussion This value must not be `nil`. The default value is `@"JSQDefaultSender"`.
* @discussion This value does not have to be unique. This value must not be `nil`.
*/
@property (copy, nonatomic) NSString *senderDisplayName;
/**
* The string identifier that uniquely identifies the current user sending messages.
*
*
* @discussion This property is used to determine if a message is incoming or outgoing.
* All message data objects returned by `collectionView:messageDataForItemAtIndexPath:` are
* checked against this identifier.
* This value must not be `nil`. The default value is `@"JSQDefaultSender"`.
* checked against this identifier. This value must not be `nil`.
*/
@property (copy, nonatomic) NSString *senderId;
/**
* Specifies whether or not the view controller should automatically scroll to the most recent message
* Specifies whether or not the view controller should automatically scroll to the most recent message
* when the view appears and when sending, receiving, and composing a new message.
*
* @discussion The default value is `YES`, which allows the view controller to scroll automatically to the most recent message.
* @discussion The default value is `YES`, which allows the view controller to scroll automatically to the most recent message.
* Set to `NO` if you want to manage scrolling yourself.
*/
@property (assign, nonatomic) BOOL automaticallyScrollsToMostRecentMessage;
@ -78,7 +82,7 @@
* @discussion This cell identifier is used for outgoing text message data items.
* The default value is the string returned by `[JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]`.
* This value must not be `nil`.
*
*
* @see JSQMessagesCollectionViewCellOutgoing.
*
* @warning Overriding this property's default value is *not* recommended.
@ -90,7 +94,7 @@
@property (copy, nonatomic) NSString *outgoingCellIdentifier;
/**
* The collection view cell identifier to use for dequeuing outgoing message collection view cells
* The collection view cell identifier to use for dequeuing outgoing message collection view cells
* in the collectionView for media messages.
*
* @discussion This cell identifier is used for outgoing media message data items.
@ -195,7 +199,7 @@
/**
* Returns the `UINib` object initialized for a `JSQMessagesViewController`.
*
* @return The initialized `UINib` object or `nil` if there were errors during initialization
* @return The initialized `UINib` object or `nil` if there were errors during initialization
* or the nib file could not be located.
*
* @discussion You may override this method to provide a customized nib. If you do,
@ -206,7 +210,7 @@
/**
* Creates and returns a new `JSQMessagesViewController` object.
*
*
* @discussion This is the designated initializer for programmatic instantiation.
*
* @return An initialized `JSQMessagesViewController` object if successful, `nil` otherwise.

View File

@ -18,8 +18,6 @@
#import "JSQMessagesViewController.h"
#import "JSQMessagesKeyboardController.h"
#import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h"
#import "JSQMessageData.h"
@ -41,6 +39,7 @@
#import "NSString+JSQMessages.h"
#import "UIColor+JSQMessages.h"
#import "UIDevice+JSQMessages.h"
#import "NSBundle+JSQMessages.h"
#import "JSQCall.h"
#import "JSQCallCollectionViewCell.h"
@ -63,8 +62,6 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
@property (weak, nonatomic) UIView *snapshotView;
@property (strong, nonatomic) JSQMessagesKeyboardController *keyboardController;
@property (assign, nonatomic) BOOL jsq_isObserving;
@property (strong, nonatomic) NSIndexPath *selectedIndexPathForMenu;
@ -110,13 +107,13 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([JSQMessagesViewController class])
bundle:[NSBundle mainBundle]];
bundle:[NSBundle bundleForClass:[JSQMessagesViewController class]]];
}
+ (instancetype)messagesViewController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([JSQMessagesViewController class])
bundle:[NSBundle mainBundle]];
bundle:[NSBundle bundleForClass:[JSQMessagesViewController class]]];
}
#pragma mark - Initialization
@ -124,27 +121,23 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (void)jsq_configureMessagesViewController
{
self.view.backgroundColor = [UIColor whiteColor];
;
self.jsq_isObserving = NO;
self.toolbarHeightConstraint.constant = kJSQMessagesInputToolbarHeightDefault;
self.toolbarHeightConstraint.constant = self.inputToolbar.preferredDefaultHeight;
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
self.inputToolbar.delegate = self;
self.inputToolbar.contentView.textView.placeHolder = NSLocalizedStringFromTable(@"New Message", @"JSQMessages", @"Placeholder text for the message input text view");
self.inputToolbar.contentView.textView.placeHolder = [NSBundle jsq_localizedStringForKey:@"new_message"];
self.inputToolbar.contentView.textView.delegate = self;
self.senderId = @"JSQDefaultSender";
self.senderDisplayName = @"JSQDefaultSender";
self.automaticallyScrollsToMostRecentMessage = YES;
self.outgoingCellIdentifier = [JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier];
self.outgoingMediaCellIdentifier = [JSQMessagesCollectionViewCellOutgoing mediaCellReuseIdentifier];
self.incomingCellIdentifier = [JSQMessagesCollectionViewCellIncoming cellReuseIdentifier];
self.incomingMediaCellIdentifier = [JSQMessagesCollectionViewCellIncoming mediaCellReuseIdentifier];
@ -153,13 +146,13 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
self.displayedMessageCellIndentifier = [JSQDisplayedMessageCollectionViewCell cellReuseIdentifier];
self.showTypingIndicator = NO;
self.showLoadEarlierMessagesHeader = NO;
self.topContentAdditionalInset = 0.0f;
[self jsq_updateCollectionViewInsets];
self.keyboardController = [[JSQMessagesKeyboardController alloc] initWithTextView:self.inputToolbar.contentView.textView
contextView:self.view
panGestureRecognizer:self.collectionView.panGestureRecognizer
@ -170,20 +163,23 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
{
[self jsq_registerForNotifications:NO];
[self jsq_removeObservers];
_collectionView.dataSource = nil;
_collectionView.delegate = nil;
_collectionView = nil;
_inputToolbar.contentView.textView.delegate = nil;
_inputToolbar.delegate = nil;
_inputToolbar = nil;
_toolbarHeightConstraint = nil;
_toolbarBottomLayoutGuide = nil;
_senderId = nil;
_senderDisplayName = nil;
_outgoingCellIdentifier = nil;
_incomingCellIdentifier = nil;
[_keyboardController endListeningForKeyboard];
_keyboardController = nil;
}
@ -195,7 +191,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (_showTypingIndicator == showTypingIndicator) {
return;
}
_showTypingIndicator = showTypingIndicator;
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView.collectionViewLayout invalidateLayout];
@ -206,7 +202,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (_showLoadEarlierMessagesHeader == showLoadEarlierMessagesHeader) {
return;
}
_showLoadEarlierMessagesHeader = showLoadEarlierMessagesHeader;
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView.collectionViewLayout invalidateLayout];
@ -224,7 +220,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (void)viewDidLoad
{
[super viewDidLoad];
[[[self class] nib] instantiateWithOwner:self options:nil];
[self jsq_configureMessagesViewController];
@ -233,17 +229,20 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (void)viewWillAppear:(BOOL)animated
{
NSParameterAssert(self.senderId != nil);
NSParameterAssert(self.senderDisplayName != nil);
[super viewWillAppear:animated];
[self.view layoutIfNeeded];
[self.collectionView.collectionViewLayout invalidateLayout];
if (self.automaticallyScrollsToMostRecentMessage) {
dispatch_async(dispatch_get_main_queue(), ^{
[self scrollToBottomAnimated:NO];
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
});
}
[self jsq_updateKeyboardTriggerPoint];
}
@ -253,7 +252,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
[self jsq_addObservers];
[self jsq_addActionToInteractivePopGestureRecognizer:YES];
[self.keyboardController beginListeningForKeyboard];
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
[self.snapshotView removeFromSuperview];
}
@ -300,6 +299,16 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
if (self.showTypingIndicator) {
self.showTypingIndicator = NO;
self.showTypingIndicator = YES;
[self.collectionView reloadData];
}
}
#pragma mark - Messages view controller
- (void)didPressSendButton:(UIButton *)button
@ -322,18 +331,18 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
}
- (void)finishSendingMessageAnimated:(BOOL)animated {
UITextView *textView = self.inputToolbar.contentView.textView;
textView.text = nil;
[textView.undoManager removeAllActions];
[self.inputToolbar toggleSendButtonEnabled];
[[NSNotificationCenter defaultCenter] postNotificationName:UITextViewTextDidChangeNotification object:textView];
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView reloadData];
if (self.automaticallyScrollsToMostRecentMessage) {
[self scrollToBottomAnimated:animated];
}
@ -345,13 +354,13 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
}
- (void)finishReceivingMessageAnimated:(BOOL)animated {
self.showTypingIndicator = NO;
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView reloadData];
if (self.automaticallyScrollsToMostRecentMessage) {
if (self.automaticallyScrollsToMostRecentMessage && ![self jsq_isMenuVisible]) {
[self scrollToBottomAnimated:animated];
}
}
@ -361,16 +370,16 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if ([self.collectionView numberOfSections] == 0) {
return;
}
NSInteger items = [self.collectionView numberOfItemsInSection:0];
if (items == 0) {
return;
}
CGFloat collectionViewContentHeight = [self.collectionView.collectionViewLayout collectionViewContentSize].height;
BOOL isContentTooSmall = (collectionViewContentHeight < self.collectionView.bounds.size.height);
BOOL isContentTooSmall = (collectionViewContentHeight < CGRectGetHeight(self.collectionView.bounds));
if (isContentTooSmall) {
// workaround for the first few messages not scrolling
// when the collection view content size is too small, `scrollToItemAtIndexPath:` doesn't work properly
@ -379,18 +388,18 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
animated:animated];
return;
}
// workaround for really long messages not scrolling
// if last message is too long, use scroll position bottom for better appearance, else use top
// possibly a UIKit bug, see #480 on GitHub
NSUInteger finalRow = MAX(0, [self.collectionView numberOfItemsInSection:0] - 1);
NSIndexPath *finalIndexPath = [NSIndexPath indexPathForItem:finalRow inSection:0];
CGSize finalCellSize = [self.collectionView.collectionViewLayout sizeForItemAtIndexPath:finalIndexPath];
CGFloat maxHeightForVisibleMessage = CGRectGetHeight(self.collectionView.bounds) - self.collectionView.contentInset.top - CGRectGetHeight(self.inputToolbar.bounds);
UICollectionViewScrollPosition scrollPosition = (finalCellSize.height > maxHeightForVisibleMessage) ? UICollectionViewScrollPositionBottom : UICollectionViewScrollPositionTop;
[self.collectionView scrollToItemAtIndexPath:finalIndexPath
atScrollPosition:scrollPosition
animated:animated];
@ -447,11 +456,12 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
{
id<JSQMessageData> messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
NSParameterAssert(messageItem != nil);
NSString *messageSenderId = [messageItem senderId];
NSParameterAssert(messageSenderId != nil);
BOOL isOutgoingMessage = [messageSenderId isEqualToString:self.senderId];
BOOL isCall = [messageItem messageType] == TSCallAdapter;
BOOL isInfoMessage = [messageItem messageType] == TSInfoMessageAdapter;
BOOL isErrorMessage = [messageItem messageType] == TSErrorMessageAdapter;
@ -463,7 +473,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
isMediaMessage = [messageItem isMediaMessage];
}
NSString *cellIdentifier = nil;
if (isCall) {
@ -549,14 +559,22 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
cellIdentifier = isOutgoingMessage ? self.outgoingCellIdentifier : self.incomingCellIdentifier;
}
JSQMessagesCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.delegate = collectionView;
if (!isMediaMessage) {
cell.textView.text = [messageItem text];
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
// workaround for iOS 7 textView data detectors bug
cell.textView.text = nil;
cell.textView.attributedText = [[NSAttributedString alloc] initWithString:[messageItem text]
attributes:@{ NSFontAttributeName : collectionView.collectionViewLayout.messageBubbleFont }];
}
NSParameterAssert(cell.textView.text != nil);
id<JSQMessageBubbleImageDataSource> bubbleImageDataSource = [collectionView.dataSource collectionView:collectionView messageBubbleImageDataForItemAtIndexPath:indexPath];
if (bubbleImageDataSource != nil) {
cell.messageBubbleImageView.image = [bubbleImageDataSource messageBubbleImage];
@ -568,7 +586,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
cell.mediaView = [messageMedia mediaView] ?: [messageMedia mediaPlaceholderView];
NSParameterAssert(cell.mediaView != nil);
}
BOOL needsAvatar = YES;
if (isOutgoingMessage && CGSizeEqualToSize(collectionView.collectionViewLayout.outgoingAvatarViewSize, CGSizeZero)) {
needsAvatar = NO;
@ -576,12 +594,12 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
else if (!isOutgoingMessage && CGSizeEqualToSize(collectionView.collectionViewLayout.incomingAvatarViewSize, CGSizeZero)) {
needsAvatar = NO;
}
id<JSQMessageAvatarImageDataSource> avatarImageDataSource = nil;
if (needsAvatar) {
avatarImageDataSource = [collectionView.dataSource collectionView:collectionView avatarImageDataForItemAtIndexPath:indexPath];
if (avatarImageDataSource != nil) {
UIImage *avatarImage = [avatarImageDataSource avatarImage];
if (avatarImage == nil) {
cell.avatarImageView.image = [avatarImageDataSource avatarPlaceholderImage];
@ -593,25 +611,25 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
}
}
}
cell.cellTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellTopLabelAtIndexPath:indexPath];
cell.messageBubbleTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:indexPath];
cell.cellBottomLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath];
CGFloat bubbleTopLabelInset = (avatarImageDataSource != nil) ? 60.0f : 15.0f;
if (isOutgoingMessage) {
cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, bubbleTopLabelInset);
}
else {
cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, bubbleTopLabelInset, 0.0f, 0.0f);
}
cell.textView.dataDetectorTypes = UIDataDetectorTypeAll;
cell.layer.shouldRasterize = YES;
cell.backgroundColor = [UIColor clearColor];
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
cell.layer.shouldRasterize = YES;
return cell;
}
@ -625,7 +643,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
else if (self.showLoadEarlierMessagesHeader && [kind isEqualToString:UICollectionElementKindSectionHeader]) {
return [collectionView dequeueLoadEarlierMessagesViewHeaderForIndexPath:indexPath];
}
return nil;
}
@ -635,7 +653,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (!self.showTypingIndicator) {
return CGSizeZero;
}
return CGSizeMake([collectionViewLayout itemWidth], kJSQMessagesTypingIndicatorFooterViewHeight);
}
@ -645,7 +663,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (!self.showLoadEarlierMessagesHeader) {
return CGSizeZero;
}
return CGSizeMake([collectionViewLayout itemWidth], kJSQMessagesLoadEarlierHeaderViewHeight);
}
@ -666,16 +684,16 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if ([messageItem isMediaMessage]) {
return NO;
}
self.selectedIndexPathForMenu = indexPath;
// textviews are selectable to allow data detectors
// however, this allows the 'copy, define, select' UIMenuController to show
// which conflicts with the collection view's UIMenuController
// temporarily disable 'selectable' to prevent this issue
JSQMessagesCollectionViewCell *selectedCell = (JSQMessagesCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
selectedCell.textView.selectable = NO;
return YES;
}
@ -684,14 +702,14 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (action == @selector(copy:)) {
return YES;
}
return NO;
}
- (void)collectionView:(JSQMessagesCollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(copy:)) {
id<JSQMessageData> messageData = [self collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
id<JSQMessageData> messageData = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
[[UIPasteboard generalPasteboard] setString:[messageData text]];
}
}
@ -707,19 +725,19 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath
{
return 1.0f;
return 0.0f;
}
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath
{
return 1.0f;
return 0.0f;
}
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath
{
return 1.0f;
return 0.0f;
}
- (void)collectionView:(JSQMessagesCollectionView *)collectionView
@ -766,9 +784,10 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (NSString *)jsq_currentlyComposedMessageText
{
// add a space to accept any auto-correct suggestions
NSString *text = self.inputToolbar.contentView.textView.text;
self.inputToolbar.contentView.textView.text = [text stringByAppendingString:@" "];
// auto-accept any auto-correct suggestions
[self.inputToolbar.contentView.textView.inputDelegate selectionWillChange:self.inputToolbar.contentView.textView];
[self.inputToolbar.contentView.textView.inputDelegate selectionDidChange:self.inputToolbar.contentView.textView];
return [self.inputToolbar.contentView.textView.text jsq_stringByTrimingWhitespace];
}
@ -779,9 +798,9 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (textView != self.inputToolbar.contentView.textView) {
return;
}
[textView becomeFirstResponder];
if (self.automaticallyScrollsToMostRecentMessage) {
[self scrollToBottomAnimated:YES];
}
@ -792,7 +811,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (textView != self.inputToolbar.contentView.textView) {
return;
}
[self.inputToolbar toggleSendButtonEnabled];
}
@ -801,7 +820,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (textView != self.inputToolbar.contentView.textView) {
return;
}
[textView resignFirstResponder];
}
@ -819,20 +838,20 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (!self.selectedIndexPathForMenu) {
return;
}
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerWillShowMenuNotification
object:nil];
UIMenuController *menu = [notification object];
[menu setMenuVisible:NO animated:NO];
JSQMessagesCollectionViewCell *selectedCell = (JSQMessagesCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:self.selectedIndexPathForMenu];
CGRect selectedCellMessageBubbleFrame = [selectedCell convertRect:selectedCell.messageBubbleContainerView.frame toView:self.view];
[menu setTargetRect:selectedCellMessageBubbleFrame inView:self.view];
[menu setMenuVisible:YES animated:YES];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveMenuWillShowNotification:)
name:UIMenuControllerWillShowMenuNotification
@ -844,7 +863,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (!self.selectedIndexPathForMenu) {
return;
}
// per comment above in 'shouldShowMenuForItemAtIndexPath:'
// re-enable 'selectable', thus re-enabling data detectors if present
JSQMessagesCollectionViewCell *selectedCell = (JSQMessagesCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:self.selectedIndexPathForMenu];
@ -857,15 +876,15 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == kJSQMessagesKeyValueObservingContext) {
if (object == self.inputToolbar.contentView.textView
&& [keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) {
CGSize oldContentSize = [[change objectForKey:NSKeyValueChangeOldKey] CGSizeValue];
CGSize newContentSize = [[change objectForKey:NSKeyValueChangeNewKey] CGSizeValue];
CGFloat dy = newContentSize.height - oldContentSize.height;
[self jsq_adjustInputToolbarForComposerTextViewContentSizeChange:dy];
[self jsq_updateCollectionViewInsets];
if (self.automaticallyScrollsToMostRecentMessage) {
@ -882,22 +901,12 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (![self.inputToolbar.contentView.textView isFirstResponder] && self.toolbarBottomLayoutGuide.constant == 0.0f) {
return;
}
CGFloat heightFromBottom = CGRectGetMaxY(self.collectionView.frame) - CGRectGetMinY(keyboardFrame);
heightFromBottom = MAX(0.0f, heightFromBottom);
[self jsq_setToolbarBottomLayoutGuideConstant:heightFromBottom];
}
- (void)keyboardControllerKeyboardDidHide:(JSQMessagesKeyboardController *)keyboardController
{
if (![self.inputToolbar.contentView.textView isFirstResponder]) {
return;
}
[self jsq_setToolbarBottomLayoutGuideConstant:0.0f];
[self.inputToolbar.contentView.textView resignFirstResponder];
CGFloat heightFromBottom = CGRectGetMaxY(self.collectionView.frame) - CGRectGetMinY(keyboardFrame);
heightFromBottom = MAX(0.0f, heightFromBottom);
[self jsq_setToolbarBottomLayoutGuideConstant:heightFromBottom];
}
- (void)jsq_setToolbarBottomLayoutGuideConstant:(CGFloat)constant
@ -905,7 +914,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
self.toolbarBottomLayoutGuide.constant = constant;
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
[self jsq_updateCollectionViewInsets];
}
@ -924,16 +933,16 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
[self.snapshotView removeFromSuperview];
}
[self.keyboardController endListeningForKeyboard];
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
[self.inputToolbar.contentView.textView resignFirstResponder];
[UIView animateWithDuration:0.0
animations:^{
[self jsq_setToolbarBottomLayoutGuideConstant:0.0f];
}];
UIView *snapshot = [self.view snapshotViewAfterScreenUpdates:YES];
[self.view addSubview:snapshot];
self.snapshotView = snapshot;
@ -946,7 +955,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateFailed:
[self.keyboardController beginListeningForKeyboard];
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
[self.snapshotView removeFromSuperview];
}
@ -960,35 +969,35 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (BOOL)jsq_inputToolbarHasReachedMaximumHeight
{
return (CGRectGetMinY(self.inputToolbar.frame) == self.topLayoutGuide.length);
return CGRectGetMinY(self.inputToolbar.frame) == (self.topLayoutGuide.length + self.topContentAdditionalInset);
}
- (void)jsq_adjustInputToolbarForComposerTextViewContentSizeChange:(CGFloat)dy
{
BOOL contentSizeIsIncreasing = (dy > 0);
if ([self jsq_inputToolbarHasReachedMaximumHeight]) {
BOOL contentOffsetIsPositive = (self.inputToolbar.contentView.textView.contentOffset.y > 0);
if (contentSizeIsIncreasing || contentOffsetIsPositive) {
[self jsq_scrollComposerTextViewToBottomAnimated:YES];
return;
}
}
CGFloat toolbarOriginY = CGRectGetMinY(self.inputToolbar.frame);
CGFloat newToolbarOriginY = toolbarOriginY - dy;
// attempted to increase origin.Y above topLayoutGuide
if (newToolbarOriginY <= self.topLayoutGuide.length) {
dy = toolbarOriginY - self.topLayoutGuide.length;
if (newToolbarOriginY <= self.topLayoutGuide.length + self.topContentAdditionalInset) {
dy = toolbarOriginY - (self.topLayoutGuide.length + self.topContentAdditionalInset);
[self jsq_scrollComposerTextViewToBottomAnimated:YES];
}
[self jsq_adjustInputToolbarHeightConstraintByDelta:dy];
[self jsq_updateKeyboardTriggerPoint];
if (dy < 0) {
[self jsq_scrollComposerTextViewToBottomAnimated:NO];
}
@ -996,26 +1005,31 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (void)jsq_adjustInputToolbarHeightConstraintByDelta:(CGFloat)dy
{
self.toolbarHeightConstraint.constant += dy;
if (self.toolbarHeightConstraint.constant < kJSQMessagesInputToolbarHeightDefault) {
self.toolbarHeightConstraint.constant = kJSQMessagesInputToolbarHeightDefault;
CGFloat proposedHeight = self.toolbarHeightConstraint.constant + dy;
CGFloat finalHeight = MAX(proposedHeight, self.inputToolbar.preferredDefaultHeight);
if (self.inputToolbar.maximumHeight != NSNotFound) {
finalHeight = MIN(finalHeight, self.inputToolbar.maximumHeight);
}
if (self.toolbarHeightConstraint.constant != finalHeight) {
self.toolbarHeightConstraint.constant = finalHeight;
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
}
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
}
- (void)jsq_scrollComposerTextViewToBottomAnimated:(BOOL)animated
{
UITextView *textView = self.inputToolbar.contentView.textView;
CGPoint contentOffsetToShowLastLine = CGPointMake(0.0f, textView.contentSize.height - CGRectGetHeight(textView.bounds));
if (!animated) {
textView.contentOffset = contentOffsetToShowLastLine;
return;
}
[UIView animateWithDuration:0.01
delay:0.01
options:UIViewAnimationOptionCurveLinear
@ -1054,12 +1068,12 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (self.jsq_isObserving) {
return;
}
[self.inputToolbar.contentView.textView addObserver:self
forKeyPath:NSStringFromSelector(@selector(contentSize))
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:kJSQMessagesKeyValueObservingContext];
self.jsq_isObserving = YES;
}
@ -1068,14 +1082,14 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (!_jsq_isObserving) {
return;
}
@try {
[_inputToolbar.contentView.textView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(contentSize))
context:kJSQMessagesKeyValueObservingContext];
}
@catch (NSException * __unused exception) { }
_jsq_isObserving = NO;
}
@ -1086,12 +1100,12 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
selector:@selector(jsq_handleDidChangeStatusBarFrameNotification:)
name:UIApplicationDidChangeStatusBarFrameNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveMenuWillShowNotification:)
name:UIMenuControllerWillShowMenuNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveMenuWillHideNotification:)
name:UIMenuControllerWillHideMenuNotification
@ -1101,11 +1115,11 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidChangeStatusBarFrameNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerWillShowMenuNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerWillHideMenuNotification
object:nil];

View File

@ -24,7 +24,7 @@
@interface JSQMessagesAvatarImageFactory ()
+ (UIImage *)jsq_circularImage:(UIImage *)image
withDiamter:(NSUInteger)diameter
withDiameter:(NSUInteger)diameter
highlightedColor:(UIColor *)highlightedColor;
+ (UIImage *)jsq_imageWitInitials:(NSString *)initials
@ -44,9 +44,9 @@
+ (JSQMessagesAvatarImage *)avatarImageWithPlaceholder:(UIImage *)placeholderImage diameter:(NSUInteger)diameter
{
UIImage *circlePlaceholderImage = [JSQMessagesAvatarImageFactory jsq_circularImage:placeholderImage
withDiamter:diameter
withDiameter:diameter
highlightedColor:nil];
return [JSQMessagesAvatarImage avatarImageWithPlaceholder:circlePlaceholderImage];
}
@ -54,7 +54,7 @@
{
UIImage *avatar = [JSQMessagesAvatarImageFactory circularAvatarImage:image withDiameter:diameter];
UIImage *highlightedAvatar = [JSQMessagesAvatarImageFactory circularAvatarHighlightedImage:image withDiameter:diameter];
return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatar
highlightedImage:highlightedAvatar
placeholderImage:avatar];
@ -63,14 +63,14 @@
+ (UIImage *)circularAvatarImage:(UIImage *)image withDiameter:(NSUInteger)diameter
{
return [JSQMessagesAvatarImageFactory jsq_circularImage:image
withDiamter:diameter
withDiameter:diameter
highlightedColor:nil];
}
+ (UIImage *)circularAvatarHighlightedImage:(UIImage *)image withDiameter:(NSUInteger)diameter
{
return [JSQMessagesAvatarImageFactory jsq_circularImage:image
withDiamter:diameter
withDiameter:diameter
highlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]];
}
@ -85,11 +85,11 @@
textColor:textColor
font:font
diameter:diameter];
UIImage *avatarHighlightedImage = [JSQMessagesAvatarImageFactory jsq_circularImage:avatarImage
withDiamter:diameter
withDiameter:diameter
highlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]];
return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatarImage
highlightedImage:avatarHighlightedImage
placeholderImage:avatarImage];
@ -108,70 +108,64 @@
NSParameterAssert(textColor != nil);
NSParameterAssert(font != nil);
NSParameterAssert(diameter > 0);
CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter);
NSString *text = [initials uppercaseStringWithLocale:[NSLocale currentLocale]];
NSDictionary *attributes = @{ NSFontAttributeName : font,
NSForegroundColorAttributeName : textColor };
CGRect textFrame = [text boundingRectWithSize:frame.size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];
CGRect textFrame = [initials boundingRectWithSize:frame.size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];
CGPoint frameMidPoint = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
CGPoint textFrameMidPoint = CGPointMake(CGRectGetMidX(textFrame), CGRectGetMidY(textFrame));
CGFloat dx = frameMidPoint.x - textFrameMidPoint.x;
CGFloat dy = frameMidPoint.y - textFrameMidPoint.y;
CGPoint drawPoint = CGPointMake(dx, dy);
UIImage *image = nil;
UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, frame);
[text drawAtPoint:drawPoint withAttributes:attributes];
[initials drawAtPoint:drawPoint withAttributes:attributes];
image = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(context);
}
UIGraphicsEndImageContext();
return [JSQMessagesAvatarImageFactory jsq_circularImage:image withDiamter:diameter highlightedColor:nil];
return [JSQMessagesAvatarImageFactory jsq_circularImage:image withDiameter:diameter highlightedColor:nil];
}
+ (UIImage *)jsq_circularImage:(UIImage *)image withDiamter:(NSUInteger)diameter highlightedColor:(UIColor *)highlightedColor
+ (UIImage *)jsq_circularImage:(UIImage *)image withDiameter:(NSUInteger)diameter highlightedColor:(UIColor *)highlightedColor
{
NSParameterAssert(image != nil);
NSParameterAssert(diameter > 0);
CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter);
UIImage *newImage = nil;
UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
UIBezierPath *imgPath = [UIBezierPath bezierPathWithOvalInRect:frame];
[imgPath addClip];
[image drawInRect:frame];
if (highlightedColor != nil) {
CGContextSetFillColorWithColor(context, highlightedColor.CGColor);
CGContextFillEllipseInRect(context, frame);
}
newImage = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(context);
}
UIGraphicsEndImageContext();

View File

@ -20,6 +20,7 @@
#import "UIColor+JSQMessages.h"
#import "UIImage+JSQMessages.h"
#import "NSBundle+JSQMessages.h"
@implementation JSQMessagesToolbarButtonFactory
@ -29,22 +30,22 @@
UIImage *accessoryImage = [UIImage jsq_defaultAccessoryImage];
UIImage *normalImage = [accessoryImage jsq_imageMaskedWithColor:[UIColor colorWithRed:0 green:71/255.f blue:1.0f alpha:1.0f]];
UIImage *highlightedImage = [accessoryImage jsq_imageMaskedWithColor:[UIColor darkGrayColor]];
UIButton *accessoryButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, accessoryImage.size.width, 32.0f)];
[accessoryButton setImage:normalImage forState:UIControlStateNormal];
[accessoryButton setImage:highlightedImage forState:UIControlStateHighlighted];
accessoryButton.contentMode = UIViewContentModeScaleAspectFit;
accessoryButton.backgroundColor = [UIColor clearColor];
//accessoryButton.tintColor = [UIColor lightGrayColor];
accessoryButton.tintColor = [UIColor lightGrayColor];
return accessoryButton;
}
+ (UIButton *)defaultSendButtonItem
{
NSString *sendTitle = NSLocalizedStringFromTable(@"Send", @"JSQMessages", @"Text for the send button on the messages view toolbar");
NSString *sendTitle = [NSBundle jsq_localizedStringForKey:@"send"];
UIButton *sendButton = [[UIButton alloc] initWithFrame:CGRectZero];
[sendButton setTitle:sendTitle forState:UIControlStateNormal];
[sendButton setTitleColor:[UIColor jsq_messageBubbleBlueColor] forState:UIControlStateNormal];
@ -57,19 +58,19 @@
sendButton.contentMode = UIViewContentModeCenter;
sendButton.backgroundColor = [UIColor clearColor];
sendButton.tintColor = [UIColor jsq_messageBubbleBlueColor];
CGFloat maxHeight = 32.0f;
CGRect sendTitleRect = [sendTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, maxHeight)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{ NSFontAttributeName : sendButton.titleLabel.font }
context:nil];
sendButton.frame = CGRectMake(0.0f,
0.0f,
CGRectGetWidth(CGRectIntegral(sendTitleRect)),
maxHeight);
return sendButton;
}

View File

@ -70,5 +70,6 @@
#import "UIColor+JSQMessages.h"
#import "UIImage+JSQMessages.h"
#import "UIView+JSQMessages.h"
#import "NSBundle+JSQMessages.h"
#endif

View File

@ -56,7 +56,12 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault;
/**
* The collection view object currently using this layout object.
*/
// TODO: fix, rename "messagesCollectionView", see #920
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincompatible-property-type"
@property (readonly, nonatomic) JSQMessagesCollectionView *collectionView;
#pragma clang diagnostic pop
/**
* Specifies whether or not the layout should enable spring behavior dynamics for its items using `UIDynamics`.

View File

@ -80,6 +80,8 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
@implementation JSQMessagesCollectionViewFlowLayout
@dynamic collectionView;
#pragma mark - Initialization
- (void)jsq_configureFlowLayout
@ -290,7 +292,7 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
[self jsq_resetDynamicAnimator];
}
if (context.emptyCache) {
if (context.invalidateFlowLayoutMessagesCache) {
[self jsq_resetLayout];
}
@ -442,7 +444,7 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
{
id<JSQMessageData> messageItem = [self.collectionView.dataSource collectionView:self.collectionView messageDataForItemAtIndexPath:indexPath];
NSValue *cachedSize = [self.messageBubbleCache objectForKey:@(messageItem.hash)];
NSValue *cachedSize = [self.messageBubbleCache objectForKey:@([messageItem messageHash])];
if (cachedSize != nil) {
return [cachedSize CGSizeValue];
}
@ -491,7 +493,7 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
finalSize = CGSizeMake(kDisplayedMessageCellWidth, kDisplayedMessageCellHeight);
}
[self.messageBubbleCache setObject:[NSValue valueWithCGSize:finalSize] forKey:@(messageItem.hash)];
[self.messageBubbleCache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageItem messageHash])];
return finalSize;
}
@ -556,6 +558,11 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
- (UIAttachmentBehavior *)jsq_springBehaviorWithLayoutAttributesItem:(UICollectionViewLayoutAttributes *)item
{
if (CGSizeEqualToSize(item.frame.size, CGSizeZero)) {
// adding a spring behavior with zero size will fail in in -prepareForCollectionViewUpdates:
return nil;
}
UIAttachmentBehavior *springBehavior = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];
springBehavior.length = 1.0f;
springBehavior.damping = 1.0f;
@ -607,7 +614,7 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
// if touch is not (0,0) -- adjust item center "in flight"
if (!CGPointEqualToPoint(CGPointZero, touchLocation)) {
CGFloat distanceFromTouch = fabsf(touchLocation.y - springBehavior.anchorPoint.y);
CGFloat distanceFromTouch = fabs(touchLocation.y - springBehavior.anchorPoint.y);
CGFloat scrollResistance = distanceFromTouch / self.springResistanceFactor;
if (self.latestDelta < 0.0f) {

View File

@ -28,10 +28,10 @@
@interface JSQMessagesCollectionViewFlowLayoutInvalidationContext : UICollectionViewFlowLayoutInvalidationContext
/**
* A boolean indicating whether to empty the layout information cache for items and views in the layout.
* A boolean indicating whether to empty the messages layout information cache for items and views in the layout.
* The default value is `NO`.
*/
@property (nonatomic, assign) BOOL emptyCache;
@property (nonatomic, assign) BOOL invalidateFlowLayoutMessagesCache;
/**
* Creates and returns a new `JSQMessagesCollectionViewFlowLayoutInvalidationContext` object.

View File

@ -28,7 +28,7 @@
if (self) {
self.invalidateFlowLayoutDelegateMetrics = NO;
self.invalidateFlowLayoutAttributes = NO;
_emptyCache = NO;
_invalidateFlowLayoutMessagesCache = NO;
}
return self;
}
@ -45,8 +45,8 @@
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: invalidateFlowLayoutDelegateMetrics=%@, invalidateFlowLayoutAttributes=%@, invalidateDataSourceCounts=%@, emptyCache=%@>",
[self class], @(self.invalidateFlowLayoutDelegateMetrics), @(self.invalidateFlowLayoutAttributes), @(self.invalidateDataSourceCounts), @(self.emptyCache)];
return [NSString stringWithFormat:@"<%@: invalidateFlowLayoutDelegateMetrics=%@, invalidateFlowLayoutAttributes=%@, invalidateDataSourceCounts=%@, invalidateFlowLayoutMessagesCache=%@>",
[self class], @(self.invalidateFlowLayoutDelegateMetrics), @(self.invalidateFlowLayoutAttributes), @(self.invalidateDataSourceCounts), @(self.invalidateFlowLayoutMessagesCache)];
}
@end

View File

@ -85,20 +85,12 @@
- (CGSize)jsq_correctedAvatarSizeFromSize:(CGSize)size
{
// cap avatar sizes to a minimum of (1.0, 1.0)
// layout constraints sometimes throw warnings when they equal 0.0
// prevent this with a size that is too small to notice
CGFloat correctedWidth = MAX(ceilf(size.width), 1.0f);
CGFloat correctedHeight = MAX(ceilf(size.height), 1.0f);
return CGSizeMake(correctedWidth, correctedHeight);
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
- (CGFloat)jsq_correctedLabelHeightForHeight:(CGFloat)height
{
// cap label heights to a minimum of 1.0
// layout constraints sometimes throw warnings when they equal 0.0
// prevent this with a size that is too small to notice
return MAX(ceilf(height), 1.0f);
return ceilf(height);
}
#pragma mark - NSObject

View File

@ -54,15 +54,21 @@ typedef enum : NSUInteger {
*/
@property (nonatomic) BOOL useThumbnail;
/**
* String to be displayed
*/
@property (nonatomic, copy) NSString *detailString;
#pragma mark - Initialization
- (instancetype)initWithCallerId:(NSString *)callerId
callerDisplayName:(NSString *)callerDisplayName
date:(NSDate *)date
status:(CallStatus)status;
status:(CallStatus)status
displayString:(NSString*)detailString;
-(NSString*)text;
-(NSString*)dateText;
-(UIImage*)thumbnailImage;

View File

@ -18,6 +18,7 @@
callerDisplayName:(NSString *)senderDisplayName
date:(NSDate *)date
status:(CallStatus)status
displayString:(NSString *)detailString
{
NSParameterAssert(senderId != nil);
NSParameterAssert(senderDisplayName != nil);
@ -29,6 +30,7 @@
_date = [date copy];
_status = status;
_messageType = TSCallAdapter;
_detailString = [detailString stringByAppendingFormat:@" "];
}
return self;
@ -36,7 +38,7 @@
-(id)init
{
NSAssert(NO,@"%s is not a valid initializer for %@. Use %@ instead", __PRETTY_FUNCTION__, [self class], NSStringFromSelector(@selector(initWithCallerId:callerDisplayName:date:status:)));
NSAssert(NO,@"%s is not a valid initializer for %@. Use %@ instead", __PRETTY_FUNCTION__, [self class], NSStringFromSelector(@selector(initWithCallerId:callerDisplayName:date:status:displayString:)));
return nil;
}
@ -47,23 +49,6 @@
_date = nil;
}
-(NSString*)text
{
NSString *name = _senderDisplayName;
switch (self.status) {
case kCallMissed:
return [NSString stringWithFormat:@"Missed call from %@. ", name];
case kCallIncoming:
return [NSString stringWithFormat:@"You received a call from %@. ", name];
case kCallOutgoing:
return [NSString stringWithFormat:@"You called %@. ", name];
default:
return nil;
break;
}
}
-(NSString*)dateText
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
@ -118,9 +103,9 @@
JSQCall * aCall = (JSQCall*)object;
return [self.senderId isEqualToString:aCall.senderId]
&& [self.senderDisplayName isEqualToString:aCall.senderDisplayName]
&& ([self.date compare:aCall.date] == NSOrderedSame)
&& self.status == aCall.status;
&& [self.senderDisplayName isEqualToString:aCall.senderDisplayName]
&& ([self.date compare:aCall.date] == NSOrderedSame)
&& self.status == aCall.status;
}
-(NSUInteger)hash
@ -163,9 +148,16 @@
return [[[self class] allocWithZone:zone]initWithCallerId:self.senderId
callerDisplayName:self.senderDisplayName
date:self.date
status:self.status];
status:self.status
displayString:self.detailString];
}
- (NSUInteger)messageHash{
return self.hash;
}
- (NSString *)text{
return _detailString;
}
@end

View File

@ -31,5 +31,8 @@
return self;
}
- (NSUInteger)messageHash {
return self.date.hash ^ self.senderId.hash;
}
@end

View File

@ -55,6 +55,12 @@
_cachedMapImageView = nil;
}
- (void)clearCachedMediaViews
{
[super clearCachedMediaViews];
_cachedMapImageView = nil;
}
#pragma mark - Setters
- (void)setLocation:(CLLocation *)location
@ -155,6 +161,11 @@
return self.cachedMapImageView;
}
- (NSUInteger)mediaHash
{
return self.hash;
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)object

View File

@ -50,4 +50,9 @@
*/
- (instancetype)initWithMaskAsOutgoing:(BOOL)maskAsOutgoing;
/**
* Clears any media view or media placeholder view that the item has cached.
*/
- (void)clearCachedMediaViews;
@end

View File

@ -44,12 +44,17 @@
if (self) {
_appliesMediaViewMaskAsOutgoing = maskAsOutgoing;
_cachedPlaceholderView = nil;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarningNotification:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
_cachedPlaceholderView = nil;
}
@ -59,6 +64,18 @@
_cachedPlaceholderView = nil;
}
- (void)clearCachedMediaViews
{
_cachedPlaceholderView = nil;
}
#pragma mark - Notifications
- (void)didReceiveMemoryWarningNotification:(NSNotification *)notification
{
[self clearCachedMediaViews];
}
#pragma mark - JSQMessageMediaData protocol
- (UIView *)mediaView
@ -72,7 +89,7 @@
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
return CGSizeMake(315.0f, 225.0f);
}
return CGSizeMake(210.0f, 150.0f);
}
@ -85,10 +102,15 @@
[JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:view isOutgoing:self.appliesMediaViewMaskAsOutgoing];
self.cachedPlaceholderView = view;
}
return self.cachedPlaceholderView;
}
- (NSUInteger)mediaHash
{
return self.hash;
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)object
@ -96,13 +118,13 @@
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
JSQMediaItem *item = (JSQMediaItem *)object;
return self.appliesMediaViewMaskAsOutgoing == item.appliesMediaViewMaskAsOutgoing;
}

View File

@ -118,6 +118,11 @@
_media = nil;
}
- (NSUInteger)messageHash
{
return self.hash;
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)object
@ -146,8 +151,7 @@
- (NSUInteger)hash
{
NSUInteger contentHash = self.isMediaMessage ? self.media.hash : self.text.hash;
NSUInteger contentHash = self.isMediaMessage ? [self.media mediaHash] : self.text.hash;
return self.senderId.hash ^ self.date.hash ^ contentHash;
}

View File

@ -75,8 +75,11 @@ typedef NS_ENUM(NSInteger, TSMessageAdapterType) {
/**
* @return An integer that can be used as a table address in a hash table structure.
*
* @discussion This value must be unique for each message with distinct contents.
* This value is used to cache layout information in the collection view.
*/
- (NSUInteger)hash;
- (NSUInteger)messageHash;
@optional

View File

@ -72,7 +72,10 @@
/**
* @return An integer that can be used as a table address in a hash table structure.
*
* @discussion This value must be unique for each media item with distinct contents.
* This value is used to cache layout information in the collection view.
*/
- (NSUInteger)hash;
- (NSUInteger)mediaHash;
@end

View File

@ -37,7 +37,7 @@
{
self = [super init];
if (self) {
_image = [UIImage imageWithCGImage:image.CGImage];
_image = [image copy];
_cachedImageView = nil;
}
return self;
@ -49,12 +49,17 @@
_cachedImageView = nil;
}
- (void)clearCachedMediaViews
{
[super clearCachedMediaViews];
_cachedImageView = nil;
}
#pragma mark - Setters
- (void)setImage:(UIImage *)image
{
_image = [UIImage imageWithCGImage:image.CGImage];
_image = [image copy];
_cachedImageView = nil;
}
@ -85,6 +90,11 @@
return self.cachedImageView;
}
- (NSUInteger)mediaHash
{
return self.hash;
}
#pragma mark - NSObject
- (NSUInteger)hash

View File

@ -52,6 +52,12 @@
_cachedVideoImageView = nil;
}
- (void)clearCachedMediaViews
{
[super clearCachedMediaViews];
_cachedVideoImageView = nil;
}
#pragma mark - Setters
- (void)setFileURL:(NSURL *)fileURL
@ -96,6 +102,11 @@
return self.cachedVideoImageView;
}
- (NSUInteger)mediaHash
{
return self.hash;
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)object

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6250" systemVersion="14C68k" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6254" systemVersion="14C109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6244"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
@ -24,7 +24,7 @@
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="warning_white.png" translatesAutoresizingMaskIntoConstraints="NO" id="ePO-Cy-jUE">
<rect key="frame" x="143" y="-3" width="35" height="35"/>
<rect key="frame" x="143" y="0.0" width="35" height="35"/>
<constraints>
<constraint firstAttribute="height" constant="35" id="Llx-81-oyV"/>
<constraint firstAttribute="width" constant="35" id="Nth-3D-Wo9"/>
@ -35,7 +35,7 @@
</view>
<constraints>
<constraint firstItem="OVa-Xw-5vl" firstAttribute="top" secondItem="eMU-z2-CzM" secondAttribute="top" id="2E8-Lm-cti"/>
<constraint firstItem="OVa-Xw-5vl" firstAttribute="top" secondItem="ePO-Cy-jUE" secondAttribute="bottom" constant="-12" id="4ie-2d-9cn"/>
<constraint firstItem="OVa-Xw-5vl" firstAttribute="top" secondItem="ePO-Cy-jUE" secondAttribute="bottom" constant="-15" id="4ie-2d-9cn"/>
<constraint firstAttribute="trailing" secondItem="OVa-Xw-5vl" secondAttribute="trailing" constant="8" id="5Mk-DE-t1M"/>
<constraint firstItem="OVa-Xw-5vl" firstAttribute="leading" secondItem="eMU-z2-CzM" secondAttribute="leading" constant="8" id="E3N-dw-XQG"/>
<constraint firstItem="OVa-Xw-5vl" firstAttribute="top" secondItem="ePO-Cy-jUE" secondAttribute="bottom" id="Yy0-Kd-Lu4"/>

View File

@ -38,14 +38,18 @@
@implementation JSQMessagesCollectionView
@dynamic dataSource;
@dynamic delegate;
@dynamic collectionViewLayout;
#pragma mark - Initialization
- (void)jsq_configureCollectionView
{
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.backgroundColor = [UIColor whiteColor];
self.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
self.keyboardDismissMode = UIScrollViewKeyboardDismissModeNone;
self.alwaysBounceVertical = YES;
self.bounces = YES;
@ -144,7 +148,7 @@
if (indexPath == nil) {
return;
}
[self.delegate collectionView:self
didTapAvatarImageView:cell.avatarImageView
atIndexPath:indexPath];
@ -156,7 +160,7 @@
if (indexPath == nil) {
return;
}
[self.delegate collectionView:self didTapMessageBubbleAtIndexPath:indexPath];
}
@ -166,7 +170,7 @@
if (indexPath == nil) {
return;
}
[self.delegate collectionView:self
didTapCellAtIndexPath:indexPath
touchLocation:position];
@ -174,8 +178,7 @@
- (void)displayedCollectionViewCellDidTapMessage:(JSQDisplayedMessageCollectionViewCell *)cell
{
NSIndexPath * indexPath = [self indexPathForCell:cell];
NSIndexPath *indexPath = [self indexPathForCell:cell];
if (indexPath == nil) {
return;
}
@ -183,4 +186,18 @@
[self.delegate collectionView:self didTapMessageBubbleAtIndexPath:indexPath];
}
- (void)messagesCollectionViewCell:(JSQMessagesCollectionViewCell *)cell didPerformAction:(SEL)action withSender:(id)sender
{
NSIndexPath *indexPath = [self indexPathForCell:cell];
if (indexPath == nil) {
return;
}
[self.delegate collectionView:self
performAction:action
forItemAtIndexPath:indexPath
withSender:sender];
}
@end

View File

@ -60,12 +60,24 @@
*/
- (void)messagesCollectionViewCellDidTapCell:(JSQMessagesCollectionViewCell *)cell atPosition:(CGPoint)position;
/**
* Tells the delegate that an actions has been selected from the menu of this cell.
* This method is automatically called for any registered actions.
*
* @param cell The cell that displayed the menu.
* @param action The action that has been performed.
* @param sender The object that initiated the action.
*
* @see `JSQMessagesCollectionViewCell`
*/
- (void)messagesCollectionViewCell:(JSQMessagesCollectionViewCell *)cell didPerformAction:(SEL)action withSender:(id)sender;
@end
/**
* The `JSQMessagesCollectionViewCell` is an abstract base class that presents the content for
* a single message data item when that item is within the collection views visible bounds.
* The `JSQMessagesCollectionViewCell` is an abstract base class that presents the content for
* a single message data item when that item is within the collection views visible bounds.
* The layout and presentation of cells is managed by the collection view and its corresponding layout object.
*
* @warning This class is intended to be subclassed. You should not use it directly.
@ -106,7 +118,7 @@
@property (weak, nonatomic, readonly) JSQMessagesCellTextView *textView;
/**
* Returns the bubble image view of the cell that is responsible for displaying message bubble images.
* Returns the bubble image view of the cell that is responsible for displaying message bubble images.
*
* @warning If mediaView returns a non-nil view, then this value will be `nil`.
*/
@ -120,7 +132,7 @@
* To do so, override `collectionView:cellForItemAtIndexPath:`
*
* @warning You should not try to manipulate any properties of this view, for example adjusting
* its frame, nor should you remove this view from the cell or remove any of its subviews.
* its frame, nor should you remove this view from the cell or remove any of its subviews.
* Doing so could result in unexpected behavior.
*/
@property (weak, nonatomic, readonly) UIView *messageBubbleContainerView;
@ -131,8 +143,7 @@
@property (weak, nonatomic, readonly) UIImageView *avatarImageView;
/**
* Returns the avatar container view of the cell. This view is the superview of
* the cell's avatarImageView.
* Returns the avatar container view of the cell. This view is the superview of the cell's avatarImageView.
*
* @discussion You may customize the cell by adding custom views to this container view.
* To do so, override `collectionView:cellForItemAtIndexPath:`
@ -161,7 +172,7 @@
/**
* Returns the `UINib` object initialized for the cell.
*
* @return The initialized `UINib` object or `nil` if there were errors during
* @return The initialized `UINib` object or `nil` if there were errors during
* initialization or the nib file could not be located.
*/
+ (UINib *)nib;
@ -180,4 +191,16 @@
*/
+ (NSString *)mediaCellReuseIdentifier;
/**
* Registers an action to be available in the cell's menu.
*
* @param action The selector to register with the cell.
*
* @discussion Non-standard or non-system actions must be added to the `UIMenuController` manually.
* You can do this by creating a new `UIMenuItem` and adding it via the controller's `menuItems` property.
*
* @warning Note that all message cells share the all actions registered here.
*/
+ (void)registerMenuAction:(SEL)action;
@end

View File

@ -27,6 +27,9 @@
#import "UIColor+JSQMessages.h"
static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
@interface JSQMessagesCollectionViewCell ()
@property (weak, nonatomic) IBOutlet JSQMessagesLabel *cellTopLabel;
@ -67,14 +70,21 @@
@end
@implementation JSQMessagesCollectionViewCell
#pragma mark - Class methods
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
jsqMessagesCollectionViewCellActions = [NSMutableSet new];
});
}
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
return [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle bundleForClass:[self class]]];
}
+ (NSString *)cellReuseIdentifier
@ -87,31 +97,36 @@
return [NSString stringWithFormat:@"%@_JSQMedia", NSStringFromClass([self class])];
}
+ (void)registerMenuAction:(SEL)action
{
[jsqMessagesCollectionViewCellActions addObject:NSStringFromSelector(action)];
}
#pragma mark - Initialization
- (void)awakeFromNib
{
[super awakeFromNib];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.backgroundColor = [UIColor whiteColor];
self.cellTopLabelHeightConstraint.constant = 0.0f;
self.messageBubbleTopLabelHeightConstraint.constant = 0.0f;
self.cellBottomLabelHeightConstraint.constant = 0.0f;
self.avatarViewSize = CGSizeZero;
self.cellTopLabel.textAlignment = NSTextAlignmentCenter;
self.cellTopLabel.font = [UIFont boldSystemFontOfSize:12.0f];
self.cellTopLabel.textColor = [UIColor lightGrayColor];
self.messageBubbleTopLabel.font = [UIFont systemFontOfSize:12.0f];
self.messageBubbleTopLabel.textColor = [UIColor lightGrayColor];
self.cellBottomLabel.font = [UIFont systemFontOfSize:11.0f];
self.cellBottomLabel.textColor = [UIColor lightGrayColor];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(jsq_handleTapGesture:)];
[self addGestureRecognizer:tap];
self.tapGestureRecognizer = tap;
@ -120,17 +135,17 @@
- (void)dealloc
{
_delegate = nil;
_cellTopLabel = nil;
_messageBubbleTopLabel = nil;
_cellBottomLabel = nil;
_textView = nil;
_messageBubbleImageView = nil;
_mediaView = nil;
_avatarImageView = nil;
[_tapGestureRecognizer removeTarget:nil action:NULL];
_tapGestureRecognizer = nil;
}
@ -140,47 +155,52 @@
- (void)prepareForReuse
{
[super prepareForReuse];
self.cellTopLabel.text = nil;
self.messageBubbleTopLabel.text = nil;
self.cellBottomLabel.text = nil;
self.textView.dataDetectorTypes = UIDataDetectorTypeNone;
self.textView.text = nil;
self.textView.attributedText = nil;
self.avatarImageView.image = nil;
self.avatarImageView.highlightedImage = nil;
}
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
return layoutAttributes;
}
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
JSQMessagesCollectionViewLayoutAttributes *customAttributes = (JSQMessagesCollectionViewLayoutAttributes *)layoutAttributes;
if (self.textView.font != customAttributes.messageBubbleFont) {
self.textView.font = customAttributes.messageBubbleFont;
}
if (!UIEdgeInsetsEqualToEdgeInsets(self.textView.textContainerInset, customAttributes.textViewTextContainerInsets)) {
self.textView.textContainerInset = customAttributes.textViewTextContainerInsets;
}
self.textViewFrameInsets = customAttributes.textViewFrameInsets;
[self jsq_updateConstraint:self.messageBubbleContainerWidthConstraint
withConstant:customAttributes.messageBubbleContainerViewWidth];
[self jsq_updateConstraint:self.cellTopLabelHeightConstraint
withConstant:customAttributes.cellTopLabelHeight];
[self jsq_updateConstraint:self.messageBubbleTopLabelHeightConstraint
withConstant:customAttributes.messageBubbleTopLabelHeight];
[self jsq_updateConstraint:self.cellBottomLabelHeightConstraint
withConstant:customAttributes.cellBottomLabelHeight];
if ([self isKindOfClass:[JSQMessagesCollectionViewCellIncoming class]]) {
self.avatarViewSize = customAttributes.incomingAvatarViewSize;
}
@ -210,25 +230,57 @@
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
self.contentView.frame = bounds;
}
}
#pragma mark - Menu actions
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([jsqMessagesCollectionViewCellActions containsObject:NSStringFromSelector(aSelector)]) {
return YES;
}
return [super respondsToSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([jsqMessagesCollectionViewCellActions containsObject:NSStringFromSelector(anInvocation.selector)]) {
__unsafe_unretained id sender;
[anInvocation getArgument:&sender atIndex:0];
[self.delegate messagesCollectionViewCell:self didPerformAction:anInvocation.selector withSender:sender];
}
else {
[super forwardInvocation:anInvocation];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([jsqMessagesCollectionViewCellActions containsObject:NSStringFromSelector(aSelector)]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
#pragma mark - Setters
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
[super setBackgroundColor:backgroundColor];
self.cellTopLabel.backgroundColor = backgroundColor;
self.messageBubbleTopLabel.backgroundColor = backgroundColor;
self.cellBottomLabel.backgroundColor = backgroundColor;
self.messageBubbleImageView.backgroundColor = backgroundColor;
self.avatarImageView.backgroundColor = backgroundColor;
self.messageBubbleContainerView.backgroundColor = backgroundColor;
self.avatarContainerView.backgroundColor = backgroundColor;
}
@ -238,7 +290,7 @@
if (CGSizeEqualToSize(avatarViewSize, self.avatarViewSize)) {
return;
}
[self jsq_updateConstraint:self.avatarContainerViewWidthConstraint withConstant:avatarViewSize.width];
[self jsq_updateConstraint:self.avatarContainerViewHeightConstraint withConstant:avatarViewSize.height];
}
@ -248,7 +300,7 @@
if (UIEdgeInsetsEqualToEdgeInsets(textViewFrameInsets, self.textViewFrameInsets)) {
return;
}
[self jsq_updateConstraint:self.textViewTopVerticalSpaceConstraint withConstant:textViewFrameInsets.top];
[self jsq_updateConstraint:self.textViewBottomVerticalSpaceConstraint withConstant:textViewFrameInsets.bottom];
[self jsq_updateConstraint:self.textViewAvatarHorizontalSpaceConstraint withConstant:textViewFrameInsets.right];
@ -257,20 +309,16 @@
- (void)setMediaView:(UIView *)mediaView
{
if ([_mediaView isEqual:mediaView]) {
return;
}
[self.messageBubbleImageView removeFromSuperview];
[self.textView removeFromSuperview];
[mediaView setTranslatesAutoresizingMaskIntoConstraints:NO];
mediaView.frame = self.messageBubbleContainerView.bounds;
[self.messageBubbleContainerView addSubview:mediaView];
[self.messageBubbleContainerView jsq_pinAllEdgesOfSubview:mediaView];
_mediaView = mediaView;
// because of cell re-use (and caching media views, if using built-in library media item)
// we may have dequeued a cell with a media view and add this one on top
// thus, remove any additional subviews hidden behind the new media view
@ -306,7 +354,7 @@
if (constraint.constant == constant) {
return;
}
constraint.constant = constant;
}
@ -315,7 +363,7 @@
- (void)jsq_handleTapGesture:(UITapGestureRecognizer *)tap
{
CGPoint touchPt = [tap locationInView:self];
if (CGRectContainsPoint(self.avatarContainerView.frame, touchPt)) {
[self.delegate messagesCollectionViewCellDidTapAvatar:self];
}
@ -330,7 +378,7 @@
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
CGPoint touchPt = [touch locationInView:self];
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
return CGRectContainsPoint(self.messageBubbleContainerView.frame, touchPt);
}

View File

@ -19,14 +19,10 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class JSQMessagesInputToolbar;
#import "JSQMessagesToolbarContentView.h"
/**
* A constant the specifies the default height for a `JSQMessagesInputToolbar`.
*/
FOUNDATION_EXPORT const CGFloat kJSQMessagesInputToolbarHeightDefault;
@class JSQMessagesInputToolbar;
/**
* The `JSQMessagesInputToolbarDelegate` protocol defines methods for interacting with
@ -59,8 +55,7 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesInputToolbarHeightDefault;
/**
* An instance of `JSQMessagesInputToolbar` defines the input toolbar for
* composing a new message. It is displayed above and follow the movement of
* the system keyboard.
* composing a new message. It is displayed above and follow the movement of the system keyboard.
*/
@interface JSQMessagesInputToolbar : UIToolbar
@ -76,7 +71,7 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesInputToolbarHeightDefault;
/**
* A boolean value indicating whether the send button is on the right side of the toolbar or not.
*
*
* @discussion The default value is `YES`, which indicates that the send button is the right-most subview of
* the toolbar's `contentView`. Set to `NO` to specify that the send button is on the left. This
* property is used to determine which touch events correspond to which actions.
@ -87,10 +82,29 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesInputToolbarHeightDefault;
*/
@property (assign, nonatomic) BOOL sendButtonOnRight;
/**
* Specifies the default (minimum) height for the toolbar. The default value is `44.0f`. This value must be positive.
*/
@property (assign, nonatomic) CGFloat preferredDefaultHeight;
/**
* Specifies the maximum height for the toolbar. The default value is `NSNotFound`, which specifies no maximum height.
*/
@property (assign, nonatomic) NSUInteger maximumHeight;
/**
* Enables or disables the send button based on whether or not its `textView` has text.
* That is, the send button will be enabled if there is text in the `textView`, and disabled otherwise.
*/
- (void)toggleSendButtonEnabled;
/**
* Loads the content view for the toolbar.
*
* @discussion Override this method to provide a custom content view for the toolbar.
*
* @return An initialized `JSQMessagesToolbarContentView` if successful, otherwise `nil`.
*/
- (JSQMessagesToolbarContentView *)loadToolbarContentView;
@end

View File

@ -26,8 +26,6 @@
#import "UIImage+JSQMessages.h"
#import "UIView+JSQMessages.h"
const CGFloat kJSQMessagesInputToolbarHeightDefault = 44.0f;
static void * kJSQMessagesInputToolbarKeyValueObservingContext = &kJSQMessagesInputToolbarKeyValueObservingContext;
@ -47,38 +45,58 @@ static void * kJSQMessagesInputToolbarKeyValueObservingContext = &kJSQMessagesIn
@implementation JSQMessagesInputToolbar
@dynamic delegate;
#pragma mark - Initialization
- (void)awakeFromNib
{
[super awakeFromNib];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.jsq_isObserving = NO;
self.sendButtonOnRight = YES;
NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([JSQMessagesToolbarContentView class]) owner:nil options:nil];
JSQMessagesToolbarContentView *toolbarContentView = [nibViews firstObject];
self.preferredDefaultHeight = 44.0f;
self.maximumHeight = NSNotFound;
JSQMessagesToolbarContentView *toolbarContentView = [self loadToolbarContentView];
toolbarContentView.frame = self.frame;
[self addSubview:toolbarContentView];
[self jsq_pinAllEdgesOfSubview:toolbarContentView];
[self setNeedsUpdateConstraints];
_contentView = toolbarContentView;
[self jsq_addObservers];
self.contentView.leftBarButtonItem = [JSQMessagesToolbarButtonFactory defaultAccessoryButtonItem];
self.contentView.rightBarButtonItem = [JSQMessagesToolbarButtonFactory defaultSendButtonItem];
[self toggleSendButtonEnabled];
}
- (JSQMessagesToolbarContentView *)loadToolbarContentView
{
NSArray *nibViews = [[NSBundle bundleForClass:[JSQMessagesInputToolbar class]] loadNibNamed:NSStringFromClass([JSQMessagesToolbarContentView class])
owner:nil
options:nil];
return nibViews.firstObject;
}
- (void)dealloc
{
[self jsq_removeObservers];
_contentView = nil;
}
#pragma mark - Setters
- (void)setPreferredDefaultHeight:(CGFloat)preferredDefaultHeight
{
NSParameterAssert(preferredDefaultHeight > 0.0f);
_preferredDefaultHeight = preferredDefaultHeight;
}
#pragma mark - Actions
- (void)jsq_leftBarButtonPressed:(UIButton *)sender
@ -111,28 +129,28 @@ static void * kJSQMessagesInputToolbarKeyValueObservingContext = &kJSQMessagesIn
{
if (context == kJSQMessagesInputToolbarKeyValueObservingContext) {
if (object == self.contentView) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(leftBarButtonItem))]) {
[self.contentView.leftBarButtonItem removeTarget:self
action:NULL
forControlEvents:UIControlEventTouchUpInside];
[self.contentView.leftBarButtonItem addTarget:self
action:@selector(jsq_leftBarButtonPressed:)
forControlEvents:UIControlEventTouchUpInside];
}
else if ([keyPath isEqualToString:NSStringFromSelector(@selector(rightBarButtonItem))]) {
[self.contentView.rightBarButtonItem removeTarget:self
action:NULL
forControlEvents:UIControlEventTouchUpInside];
[self.contentView.rightBarButtonItem addTarget:self
action:@selector(jsq_rightBarButtonPressed:)
forControlEvents:UIControlEventTouchUpInside];
}
[self toggleSendButtonEnabled];
}
}
@ -143,17 +161,17 @@ static void * kJSQMessagesInputToolbarKeyValueObservingContext = &kJSQMessagesIn
if (self.jsq_isObserving) {
return;
}
[self.contentView addObserver:self
forKeyPath:NSStringFromSelector(@selector(leftBarButtonItem))
options:0
context:kJSQMessagesInputToolbarKeyValueObservingContext];
[self.contentView addObserver:self
forKeyPath:NSStringFromSelector(@selector(rightBarButtonItem))
options:0
context:kJSQMessagesInputToolbarKeyValueObservingContext];
self.jsq_isObserving = YES;
}
@ -162,12 +180,12 @@ static void * kJSQMessagesInputToolbarKeyValueObservingContext = &kJSQMessagesIn
if (!_jsq_isObserving) {
return;
}
@try {
[_contentView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(leftBarButtonItem))
context:kJSQMessagesInputToolbarKeyValueObservingContext];
[_contentView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(rightBarButtonItem))
context:kJSQMessagesInputToolbarKeyValueObservingContext];

View File

@ -19,6 +19,9 @@
#import "JSQMessagesLoadEarlierHeaderView.h"
#import "NSBundle+JSQMessages.h"
const CGFloat kJSQMessagesLoadEarlierHeaderViewHeight = 32.0f;
@ -39,7 +42,7 @@ const CGFloat kJSQMessagesLoadEarlierHeaderViewHeight = 32.0f;
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([JSQMessagesLoadEarlierHeaderView class])
bundle:[NSBundle mainBundle]];
bundle:[NSBundle bundleForClass:[JSQMessagesLoadEarlierHeaderView class]]];
}
+ (NSString *)headerReuseIdentifier
@ -53,9 +56,11 @@ const CGFloat kJSQMessagesLoadEarlierHeaderViewHeight = 32.0f;
{
[super awakeFromNib];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.backgroundColor = [UIColor clearColor];
[self.loadButton setTitle:NSLocalizedStringFromTable(@"Load Earlier Messages", @"JSQMessages", @"Text for button to load previously sent messages")
forState:UIControlStateNormal];
[self.loadButton setTitle:[NSBundle jsq_localizedStringForKey:@"load_earlier_messages"] forState:UIControlStateNormal];
self.loadButton.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
}
- (void)dealloc

View File

@ -47,7 +47,7 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([JSQMessagesToolbarContentView class])
bundle:[NSBundle mainBundle]];
bundle:[NSBundle bundleForClass:[JSQMessagesToolbarContentView class]]];
}
#pragma mark - Initialization
@ -57,10 +57,10 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
[super awakeFromNib];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.leftHorizontalSpacingConstraint.constant = kJSQMessagesToolbarContentViewHorizontalSpacingDefault;
self.rightHorizontalSpacingConstraint.constant = kJSQMessagesToolbarContentViewHorizontalSpacingDefault;
self.backgroundColor = [UIColor clearColor];
}
@ -87,7 +87,7 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
if (_leftBarButtonItem) {
[_leftBarButtonItem removeFromSuperview];
}
if (!leftBarButtonItem) {
_leftBarButtonItem = nil;
self.leftHorizontalSpacingConstraint.constant = 0.0f;
@ -95,19 +95,21 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
self.leftBarButtonContainerView.hidden = YES;
return;
}
if (CGRectEqualToRect(leftBarButtonItem.frame, CGRectZero)) {
leftBarButtonItem.frame = self.leftBarButtonContainerView.bounds;
}
self.leftBarButtonContainerView.hidden = NO;
self.leftHorizontalSpacingConstraint.constant = kJSQMessagesToolbarContentViewHorizontalSpacingDefault;
self.leftBarButtonItemWidth = CGRectGetWidth(leftBarButtonItem.frame);
[leftBarButtonItem setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.leftBarButtonContainerView addSubview:leftBarButtonItem];
[self.leftBarButtonContainerView jsq_pinAllEdgesOfSubview:leftBarButtonItem];
[self setNeedsUpdateConstraints];
_leftBarButtonItem = leftBarButtonItem;
}
@ -122,7 +124,7 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
if (_rightBarButtonItem) {
[_rightBarButtonItem removeFromSuperview];
}
if (!rightBarButtonItem) {
_rightBarButtonItem = nil;
self.rightHorizontalSpacingConstraint.constant = 0.0f;
@ -130,19 +132,21 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
self.rightBarButtonContainerView.hidden = YES;
return;
}
if (CGRectEqualToRect(rightBarButtonItem.frame, CGRectZero)) {
rightBarButtonItem.frame = self.rightBarButtonContainerView.bounds;
}
self.rightBarButtonContainerView.hidden = NO;
self.rightHorizontalSpacingConstraint.constant = kJSQMessagesToolbarContentViewHorizontalSpacingDefault;
self.rightBarButtonItemWidth = CGRectGetWidth(rightBarButtonItem.frame);
[rightBarButtonItem setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.rightBarButtonContainerView addSubview:rightBarButtonItem];
[self.rightBarButtonContainerView jsq_pinAllEdgesOfSubview:rightBarButtonItem];
[self setNeedsUpdateConstraints];
_rightBarButtonItem = rightBarButtonItem;
}

View File

@ -44,7 +44,7 @@ const CGFloat kJSQMessagesTypingIndicatorFooterViewHeight = 46.0f;
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([JSQMessagesTypingIndicatorFooterView class])
bundle:[NSBundle mainBundle]];
bundle:[NSBundle bundleForClass:[JSQMessagesTypingIndicatorFooterView class]]];
}
+ (NSString *)footerReuseIdentifier

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2014 Jesse Squires
Copyright (c) 2013-present Jesse Squires
http://www.hexedbits.com

View File

@ -5,7 +5,7 @@ platform :ios, '7.0'
# ignore all warnings from all pods
inhibit_all_warnings!
pod 'JSQSystemSoundPlayer'
pod 'JSQSystemSoundPlayer', '~> 2.0'
target :JSQMessagesTests, :exclusive => true do
pod 'OCMock'

View File

@ -1,13 +1,13 @@
PODS:
- JSQSystemSoundPlayer (2.0.0)
- OCMock (3.1.1)
- JSQSystemSoundPlayer (2.0.1)
- OCMock (3.1.2)
DEPENDENCIES:
- JSQSystemSoundPlayer
- JSQSystemSoundPlayer (~> 2.0)
- OCMock
SPEC CHECKSUMS:
JSQSystemSoundPlayer: c98443b1cbb3b45db09d0d3d6c2355cf78294981
OCMock: f6cb8c162ab9d5620dddf411282c7b2c0ee78854
JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
COCOAPODS: 0.35.0
COCOAPODS: 0.37.2

View File

@ -0,0 +1 @@
../../../JSQSystemSoundPlayer/JSQSystemSoundPlayer/Classes/JSQSystemSoundPlayer.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/NSInvocation+OCMAdditions.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/NSObject+OCMAdditions.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/NSValue+OCMAdditions.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCClassMockObject.h

1
Pods/Headers/Private/OCMock/OCMArg.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMArg.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMBlockCaller.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMConstraint.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMExpectationRecorder.h

1
Pods/Headers/Private/OCMock/OCMFunctions.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMFunctions.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMInvocationExpectation.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMInvocationMatcher.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMInvocationStub.h

1
Pods/Headers/Private/OCMock/OCMLocation.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMLocation.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMMacroState.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMNotificationPoster.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMObserverRecorder.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMPassByRefSetter.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMRealObjectForwarder.h

1
Pods/Headers/Private/OCMock/OCMRecorder.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMRecorder.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMReturnValueProvider.h

View File

@ -0,0 +1 @@
../../../OCMock/Source/OCMock/OCMStubRecorder.h

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