Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1de1300f2a | ||
|
|
225b1baa11 | ||
|
|
563f505da4 | ||
|
|
e5582fef8a | ||
|
|
533b4bef6c | ||
|
|
867115b03c | ||
|
|
c2651dd0a4 | ||
|
|
ec8dcf66d5 | ||
|
|
acbb692b3d | ||
|
|
7606339770 | ||
|
|
9551159a20 | ||
|
|
c8831f0b30 | ||
|
|
566ddb3301 | ||
|
|
ea373b0130 | ||
|
|
5ac93a25e3 | ||
|
|
8e8f0998be | ||
|
|
27ec773edc | ||
|
|
34f046c452 | ||
|
|
7e7edbf675 | ||
|
|
98368fc105 | ||
|
|
08567a4be6 | ||
|
|
78c46c0ac4 | ||
|
|
2f94744368 | ||
|
|
ad761def43 | ||
|
|
c5c9c20246 | ||
|
|
c4d38a8407 | ||
|
|
9db018bfce | ||
|
|
0db2ab9e74 | ||
|
|
6a5aeb5ffb | ||
|
|
74acd93417 | ||
|
|
3247b45b13 | ||
|
|
bccad24efc | ||
|
|
5e9e368f4d | ||
|
|
bff4ae7b86 | ||
|
|
4c05c28f70 | ||
|
|
6b58a211c5 | ||
|
|
cb901cb026 | ||
|
|
cd974aa015 | ||
|
|
a6a2884b07 | ||
|
|
a859a6862a | ||
|
|
72e7343908 | ||
|
|
45590c3405 | ||
|
|
cd085c683f | ||
|
|
853a3fe1c5 | ||
|
|
7610526965 | ||
|
|
ab831aaf67 | ||
|
|
5c57cee47f | ||
|
|
dcebef9d1e | ||
|
|
547cec7287 | ||
|
|
72540e2553 | ||
|
|
1c5811107c | ||
|
|
ea42262a9a | ||
|
|
483f36402a | ||
|
|
7edc1312d1 | ||
|
|
6086d26bc8 | ||
|
|
e75c2fb166 | ||
|
|
0ed53ae8c5 | ||
|
|
fb7119d3b2 | ||
|
|
71a7f519fc | ||
|
|
9be8f6ed65 | ||
|
|
2beab5be52 | ||
|
|
10e1fc80a4 | ||
|
|
62c6844ce6 | ||
|
|
dd0e214d13 | ||
|
|
25a887a31e | ||
|
|
bd82c11af6 | ||
|
|
3e69f1fc1d | ||
|
|
acb047f25d | ||
|
|
fc581c8b0b | ||
|
|
007a945959 | ||
|
|
56f8a9c648 | ||
|
|
0f5cd6c2fd | ||
|
|
86746bd9d6 | ||
|
|
94b19ccb08 | ||
|
|
88d2fe4ef4 | ||
|
|
97bfef24c9 | ||
|
|
f09dc0230e | ||
|
|
a30f9bdee9 | ||
|
|
e23a248797 | ||
|
|
05c97700a2 | ||
|
|
1f64013617 | ||
|
|
a6437be1ab | ||
|
|
e0ae6cdb1f | ||
|
|
d9aea32ed0 | ||
|
|
09db017b87 | ||
|
|
a180dcbee6 | ||
|
|
2698a699d8 | ||
|
|
0aa0ae2237 | ||
|
|
1fb791f964 | ||
|
|
0b8fb267d1 | ||
|
|
b728ff3b4a | ||
|
|
fa7e42bfa5 | ||
|
|
bfb0c58785 | ||
|
|
c0a35d832b | ||
|
|
82534ea75d | ||
|
|
08cc2d14d4 | ||
|
|
720999efe9 | ||
|
|
0157f1a47e | ||
|
|
fc340cd166 | ||
|
|
399544ff5d | ||
|
|
245551f8ce | ||
|
|
27d7a2e09c | ||
|
|
9e6e0f2524 | ||
|
|
9893d4208f | ||
|
|
3e80be42db | ||
|
|
e354981b49 | ||
|
|
08377bc153 | ||
|
|
1acb68119e | ||
|
|
f31b214c1b | ||
|
|
ddf11e62e4 | ||
|
|
fa668ef7da | ||
|
|
e3a2babfb2 | ||
|
|
ed81bb2977 | ||
|
|
6e7647d14a | ||
|
|
99977d4938 | ||
|
|
df26f3cc16 | ||
|
|
1b9814ee5c | ||
|
|
eec8ff4236 | ||
|
|
3779613da8 | ||
|
|
f55bdad953 | ||
|
|
741f53f793 | ||
|
|
9bbdffa568 | ||
|
|
db40c8b588 | ||
|
|
dbad9a9325 | ||
|
|
5d21d699f6 | ||
|
|
b5ca3df47e | ||
|
|
191d2c3700 | ||
|
|
bbbb242521 | ||
|
|
174cae9b6c | ||
|
|
7d6534eeae |
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
# Xcode
|
||||
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
/build/*
|
||||
*/build/*
|
||||
*.pbxuser
|
||||
@ -18,8 +18,3 @@ DerivedData
|
||||
.idea/
|
||||
*.hmap
|
||||
*.xccheckout
|
||||
|
||||
|
||||
#CocoaPods
|
||||
|
||||
Pods
|
||||
@ -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
|
||||
|
||||
@ -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 = (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
46
JSQMessagesTests/CategoryTests/JSQMessagesNSBundleTests.m
Normal file
46
JSQMessagesTests/CategoryTests/JSQMessagesNSBundleTests.m
Normal 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
|
||||
@ -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");
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
@ -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";
|
||||
@ -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";
|
||||
@ -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";
|
||||
@ -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";
|
||||
@ -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" = "הודעה חדשה";
|
||||
@ -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";
|
||||
@ -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" = "新しいメッセージ";
|
||||
@ -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";
|
||||
@ -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ść";
|
||||
@ -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";
|
||||
@ -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";
|
||||
@ -22,8 +22,8 @@
|
||||
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
|
||||
// ********************************
|
||||
|
||||
"Load Earlier Messages" = "Предыдущие сообщения";
|
||||
"load_earlier_messages" = "Предыдущие сообщения";
|
||||
|
||||
"Send" = "Отпр";
|
||||
"send" = "Отпр";
|
||||
|
||||
"New Message" = "Сообщение";
|
||||
"new_message" = "Сообщение";
|
||||
@ -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";
|
||||
@ -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" = "新信息";
|
||||
@ -22,8 +22,8 @@
|
||||
// https://github.com/jessesquires/JSQMessagesViewController/issues/237
|
||||
// ********************************
|
||||
|
||||
"Load Earlier Messages" = "载入较早的信息";
|
||||
"load_earlier_messages" = "載入之前的訊息";
|
||||
|
||||
"Send" = "发送";
|
||||
"send" = "傳送";
|
||||
|
||||
"New Message" = "新信息";
|
||||
"new_message" = "新信息";
|
||||
@ -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" = "הודעה חדשה";
|
||||
@ -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";
|
||||
@ -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ść";
|
||||
@ -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";
|
||||
@ -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";
|
||||
@ -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
|
||||
|
||||
42
JSQMessagesViewController/Categories/NSBundle+JSQMessages.h
Normal file
42
JSQMessagesViewController/Categories/NSBundle+JSQMessages.h
Normal 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
|
||||
42
JSQMessagesViewController/Categories/NSBundle+JSQMessages.m
Normal file
42
JSQMessagesViewController/Categories/NSBundle+JSQMessages.m
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -70,5 +70,6 @@
|
||||
#import "UIColor+JSQMessages.h"
|
||||
#import "UIImage+JSQMessages.h"
|
||||
#import "UIView+JSQMessages.h"
|
||||
#import "NSBundle+JSQMessages.h"
|
||||
|
||||
#endif
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -31,5 +31,8 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)messageHash {
|
||||
return self.date.hash ^ self.senderId.hash;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -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
|
||||
|
||||
@ -50,4 +50,9 @@
|
||||
*/
|
||||
- (instancetype)initWithMaskAsOutgoing:(BOOL)maskAsOutgoing;
|
||||
|
||||
/**
|
||||
* Clears any media view or media placeholder view that the item has cached.
|
||||
*/
|
||||
- (void)clearCachedMediaViews;
|
||||
|
||||
@end
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"/>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 view’s 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 view’s 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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
|
||||
MIT License
|
||||
Copyright (c) 2014 Jesse Squires
|
||||
Copyright (c) 2013-present Jesse Squires
|
||||
|
||||
http://www.hexedbits.com
|
||||
|
||||
|
||||
2
Podfile
2
Podfile
@ -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'
|
||||
|
||||
12
Podfile.lock
12
Podfile.lock
@ -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
|
||||
|
||||
1
Pods/Headers/Private/JSQSystemSoundPlayer/JSQSystemSoundPlayer.h
generated
Symbolic link
1
Pods/Headers/Private/JSQSystemSoundPlayer/JSQSystemSoundPlayer.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../JSQSystemSoundPlayer/JSQSystemSoundPlayer/Classes/JSQSystemSoundPlayer.h
|
||||
1
Pods/Headers/Private/OCMock/NSInvocation+OCMAdditions.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/NSInvocation+OCMAdditions.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/NSInvocation+OCMAdditions.h
|
||||
1
Pods/Headers/Private/OCMock/NSMethodSignature+OCMAdditions.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/NSMethodSignature+OCMAdditions.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.h
|
||||
1
Pods/Headers/Private/OCMock/NSNotificationCenter+OCMAdditions.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/NSNotificationCenter+OCMAdditions.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.h
|
||||
1
Pods/Headers/Private/OCMock/NSObject+OCMAdditions.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/NSObject+OCMAdditions.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/NSObject+OCMAdditions.h
|
||||
1
Pods/Headers/Private/OCMock/NSValue+OCMAdditions.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/NSValue+OCMAdditions.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/NSValue+OCMAdditions.h
|
||||
1
Pods/Headers/Private/OCMock/OCClassMockObject.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCClassMockObject.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCClassMockObject.h
|
||||
1
Pods/Headers/Private/OCMock/OCMArg.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMArg.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMArg.h
|
||||
1
Pods/Headers/Private/OCMock/OCMBlockCaller.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMBlockCaller.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMBlockCaller.h
|
||||
1
Pods/Headers/Private/OCMock/OCMBoxedReturnValueProvider.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMBoxedReturnValueProvider.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h
|
||||
1
Pods/Headers/Private/OCMock/OCMConstraint.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMConstraint.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMConstraint.h
|
||||
1
Pods/Headers/Private/OCMock/OCMExceptionReturnValueProvider.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMExceptionReturnValueProvider.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h
|
||||
1
Pods/Headers/Private/OCMock/OCMExpectationRecorder.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMExpectationRecorder.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMExpectationRecorder.h
|
||||
1
Pods/Headers/Private/OCMock/OCMFunctions.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMFunctions.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMFunctions.h
|
||||
1
Pods/Headers/Private/OCMock/OCMIndirectReturnValueProvider.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMIndirectReturnValueProvider.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h
|
||||
1
Pods/Headers/Private/OCMock/OCMInvocationExpectation.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMInvocationExpectation.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMInvocationExpectation.h
|
||||
1
Pods/Headers/Private/OCMock/OCMInvocationMatcher.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMInvocationMatcher.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMInvocationMatcher.h
|
||||
1
Pods/Headers/Private/OCMock/OCMInvocationStub.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMInvocationStub.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMInvocationStub.h
|
||||
1
Pods/Headers/Private/OCMock/OCMLocation.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMLocation.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMLocation.h
|
||||
1
Pods/Headers/Private/OCMock/OCMMacroState.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMMacroState.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMMacroState.h
|
||||
1
Pods/Headers/Private/OCMock/OCMNotificationPoster.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMNotificationPoster.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMNotificationPoster.h
|
||||
1
Pods/Headers/Private/OCMock/OCMObserverRecorder.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMObserverRecorder.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMObserverRecorder.h
|
||||
1
Pods/Headers/Private/OCMock/OCMPassByRefSetter.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMPassByRefSetter.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMPassByRefSetter.h
|
||||
1
Pods/Headers/Private/OCMock/OCMRealObjectForwarder.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMRealObjectForwarder.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMRealObjectForwarder.h
|
||||
1
Pods/Headers/Private/OCMock/OCMRecorder.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMRecorder.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMRecorder.h
|
||||
1
Pods/Headers/Private/OCMock/OCMReturnValueProvider.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMReturnValueProvider.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMReturnValueProvider.h
|
||||
1
Pods/Headers/Private/OCMock/OCMStubRecorder.h
generated
Symbolic link
1
Pods/Headers/Private/OCMock/OCMStubRecorder.h
generated
Symbolic link
@ -0,0 +1 @@
|
||||
../../../OCMock/Source/OCMock/OCMStubRecorder.h
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user