Compare commits

..

No commits in common. "fix-crash-on-delete" and "master" have entirely different histories.

210 changed files with 734 additions and 8541 deletions

9
.gitignore vendored
View File

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

View File

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

View File

@ -30,8 +30,6 @@
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 */; };
@ -103,6 +101,7 @@
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 */; };
@ -143,6 +142,21 @@
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; };
@ -158,9 +172,6 @@
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>"; };
@ -353,12 +364,12 @@
636A8663AEEE5C37B65C515D /* Frameworks */ = {
isa = PBXGroup;
children = (
88445B3419E0AE4A0014F889 /* CoreGraphics.framework */,
88445B4319E1B5110014F889 /* MapKit.framework */,
88445B4119E1B50B0014F889 /* CoreLocation.framework */,
88445B3419E0AE4A0014F889 /* CoreGraphics.framework */,
88445B3219E0AE450014F889 /* Foundation.framework */,
782026E9E518622532ED474D /* libPods-JSQMessagesTests.a */,
97E6750B77E8A7042BA0754B /* libPods.a */,
88445B4319E1B5110014F889 /* MapKit.framework */,
88445B3619E0AE5C0014F889 /* QuartzCore.framework */,
88445B3019E0AE3F0014F889 /* UIKit.framework */,
88445B3A19E0C0B10014F889 /* XCTest.framework */,
@ -367,6 +378,14 @@
path = ../..;
sourceTree = "<group>";
};
8841B88219F4983C00EA16B6 /* Strings */ = {
isa = PBXGroup;
children = (
8841B88719F4988800EA16B6 /* JSQMessages.strings */,
);
path = Strings;
sourceTree = "<group>";
};
88A25EF919D8DEC400924534 = {
isa = PBXGroup;
children = (
@ -464,8 +483,6 @@
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 */,
@ -603,7 +620,6 @@
88A25FE219D8E18400924534 /* CategoryTests */ = {
isa = PBXGroup;
children = (
8873B60D1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m */,
88A25FE319D8E18400924534 /* JSQMessagesNSStringTests.m */,
88A25FE419D8E18400924534 /* JSQMessagesUIColorTests.m */,
88A25FE519D8E18400924534 /* JSQMessagesUIImageTests.m */,
@ -789,6 +805,7 @@
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 */,
@ -935,7 +952,6 @@
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 */,
);
@ -969,7 +985,6 @@
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 */,
@ -988,6 +1003,28 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
8841B88719F4988800EA16B6 /* JSQMessages.strings */ = {
isa = PBXVariantGroup;
children = (
8841B88619F4988800EA16B6 /* Base */,
8841B88819F4988900EA16B6 /* en */,
8841B88919F4988A00EA16B6 /* es */,
8841B88A19F4988B00EA16B6 /* de */,
8841B88B19F4988C00EA16B6 /* zh-Hans */,
8841B88C19F4988F00EA16B6 /* zh-Hant */,
8841B88D19F4989000EA16B6 /* ro */,
8841B88E19F4989100EA16B6 /* pl */,
8841B88F19F4989200EA16B6 /* ru */,
8841B89019F4989200EA16B6 /* pt */,
8841B89119F4989300EA16B6 /* fr */,
8841B89219F4989400EA16B6 /* it */,
8841B89319F4989500EA16B6 /* he */,
8841B89419F4989500EA16B6 /* nl */,
8841B89519F4989600EA16B6 /* tr */,
);
name = JSQMessages.strings;
sourceTree = "<group>";
};
88A25F3019D8DF2500924534 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7531" systemVersion="14D131" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="JRd-Be-psV">
<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">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7520"/>
<deployment defaultVersion="1792" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
</dependencies>
<scenes>
<!--Root View Controller-->
@ -80,9 +80,10 @@
<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 firstAttribute="centerY" secondItem="K7N-os-fuc" secondAttribute="centerY" id="j7N-59-vRk"/>
<constraint firstItem="K7N-os-fuc" firstAttribute="top" secondItem="qU9-o3-MWC" secondAttribute="top" constant="6" id="UbM-aE-caS"/>
<constraint firstAttribute="bottom" secondItem="bSS-CD-nfD" secondAttribute="bottom" constant="9" id="poL-wE-Eir"/>
</constraints>
</tableViewCellContentView>
@ -110,10 +111,11 @@
</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>
@ -143,11 +145,12 @@
</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 firstAttribute="centerY" secondItem="baL-Tb-bTJ" secondAttribute="centerY" id="hx4-cn-W0h"/>
<constraint firstItem="baL-Tb-bTJ" firstAttribute="top" secondItem="OFq-Mz-mbl" secondAttribute="top" constant="6" id="v5e-xk-srk"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
@ -178,12 +181,13 @@
</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>
@ -210,10 +214,11 @@
</label>
</subviews>
<constraints>
<constraint firstAttribute="centerY" secondItem="LuM-mk-Zj6" secondAttribute="centerY" id="2oC-JM-LVI"/>
<constraint firstAttribute="bottom" secondItem="LuM-mk-Zj6" secondAttribute="bottom" constant="6" id="1G7-8S-rYC"/>
<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>
@ -248,9 +253,10 @@
<constraints>
<constraint firstItem="3d2-fZ-dx9" firstAttribute="leading" secondItem="btE-Mk-fSE" secondAttribute="leading" constant="8" id="6Cs-6l-Xzp"/>
<constraint firstItem="uXC-2j-cgi" firstAttribute="leading" secondItem="3d2-fZ-dx9" secondAttribute="trailing" constant="8" id="7Mq-HR-4nY"/>
<constraint firstItem="uXC-2j-cgi" firstAttribute="top" secondItem="btE-Mk-fSE" secondAttribute="top" constant="6" id="Ajq-5g-XKo"/>
<constraint firstAttribute="bottom" secondItem="3d2-fZ-dx9" secondAttribute="bottom" constant="9" id="EzH-aB-ePQ"/>
<constraint firstAttribute="bottom" secondItem="uXC-2j-cgi" secondAttribute="bottom" constant="6" id="RCb-Mi-FHX"/>
<constraint firstItem="3d2-fZ-dx9" firstAttribute="top" secondItem="btE-Mk-fSE" secondAttribute="top" constant="10" id="btF-q4-HJg"/>
<constraint firstAttribute="centerY" secondItem="uXC-2j-cgi" secondAttribute="centerY" id="zJf-Xj-C1X"/>
<constraint firstAttribute="trailing" secondItem="uXC-2j-cgi" secondAttribute="trailing" constant="8" id="zzu-aP-ZiL"/>
</constraints>
</tableViewCellContentView>

View File

@ -74,26 +74,13 @@
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
@ -520,43 +507,6 @@
#pragma mark - UICollectionView Delegate
#pragma mark - Custom menu items
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(customAction:)) {
return YES;
}
return [super collectionView:collectionView canPerformAction:action forItemAtIndexPath:indexPath withSender:sender];
}
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(customAction:)) {
[self customAction:sender];
return;
}
[super collectionView:collectionView performAction:action forItemAtIndexPath:indexPath withSender:sender];
}
- (void)customAction:(id)sender
{
NSLog(@"Custom action received! Sender: %@", sender);
[[[UIAlertView alloc] initWithTitle:@"Custom Action"
message:nil
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil]
show];
}
#pragma mark - JSQMessages collection view flow layout delegate
#pragma mark - Adjusting cell label heights

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,26 +1,21 @@
Pod::Spec.new do |s|
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.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.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'
s.dependency 'JSQSystemSoundPlayer', '~> 2.0.0'
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,58 +18,34 @@
#import "JSQSystemSoundPlayer+JSQMessages.h"
#import "NSBundle+JSQMessages.h"
static NSString * const kJSQMessageReceivedSoundName = @"message_received";
static NSString * const kJSQMessageSentSoundName = @"message_sent";
static NSString * const kJSQMessageReceivedSoundName = @"JSQMessagesAssets.bundle/Sounds/message_received";
static NSString * const kJSQMessageSentSoundName = @"JSQMessagesAssets.bundle/Sounds/message_sent";
@implementation JSQSystemSoundPlayer (JSQMessages)
#pragma mark - Public
+ (void)jsq_playMessageReceivedSound
{
[self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageReceivedSoundName asAlert:NO];
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithFilename:kJSQMessageReceivedSoundName
fileExtension:kJSQSystemSoundTypeAIFF];
}
+ (void)jsq_playMessageReceivedAlert
{
[self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageReceivedSoundName asAlert:YES];
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithFilename:kJSQMessageReceivedSoundName
fileExtension:kJSQSystemSoundTypeAIFF];
}
+ (void)jsq_playMessageSentSound
{
[self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageSentSoundName asAlert:NO];
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithFilename:kJSQMessageSentSoundName
fileExtension:kJSQSystemSoundTypeAIFF];
}
+ (void)jsq_playMessageSentAlert
{
[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];
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithFilename:kJSQMessageSentSoundName
fileExtension:kJSQSystemSoundTypeAIFF];
}
@end

View File

@ -1,42 +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
//
#import <Foundation/Foundation.h>
@interface NSBundle (JSQMessages)
/**
* @return The bundle for JSQMessagesViewController.
*/
+ (NSBundle *)jsq_messagesBundle;
/**
* @return The bundle for assets in JSQMessagesViewController.
*/
+ (NSBundle *)jsq_messagesAssetBundle;
/**
* Returns a localized version of the string designated by the specified key and residing in the JSQMessages table.
*
* @param key The key for a string in the JSQMessages table.
*
* @return A localized version of the string designated by key in the JSQMessages table.
*/
+ (NSString *)jsq_localizedStringForKey:(NSString *)key;
@end

View File

@ -1,42 +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
//
#import "NSBundle+JSQMessages.h"
#import "JSQMessagesViewController.h"
@implementation NSBundle (JSQMessages)
+ (NSBundle *)jsq_messagesBundle
{
return [NSBundle bundleForClass:[JSQMessagesViewController class]];
}
+ (NSBundle *)jsq_messagesAssetBundle
{
NSString *bundleResourcePath = [NSBundle jsq_messagesBundle].resourcePath;
NSString *assetPath = [bundleResourcePath stringByAppendingPathComponent:@"JSQMessagesAssets.bundle"];
return [NSBundle bundleWithPath:assetPath];
}
+ (NSString *)jsq_localizedStringForKey:(NSString *)key
{
return NSLocalizedStringFromTableInBundle(key, @"JSQMessages", [NSBundle jsq_messagesAssetBundle], nil);
}
@end

View File

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

View File

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

View File

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

View File

@ -51,8 +51,8 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
- (void)jsq_handleKeyboardNotification:(NSNotification *)notification completion:(JSQAnimationCompletionBlock)completion;
- (void)jsq_setKeyboardViewHidden:(BOOL)hidden;
- (void)jsq_notifyKeyboardFrameNotificationForFrame:(CGRect)frame;
- (void)jsq_resetKeyboardAndTextView;
- (void)jsq_removeKeyboardFrameObserver;
@ -75,7 +75,7 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
NSParameterAssert(textView != nil);
NSParameterAssert(contextView != nil);
NSParameterAssert(panGestureRecognizer != nil);
self = [super init];
if (self) {
_textView = textView;
@ -105,15 +105,15 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
if (_keyboardView) {
[self jsq_removeKeyboardFrameObserver];
}
_keyboardView = keyboardView;
if (keyboardView && !_jsq_isObserving) {
[_keyboardView addObserver:self
forKeyPath:NSStringFromSelector(@selector(frame))
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:kJSQMessagesKeyboardControllerKeyValueObservingContext];
_jsq_isObserving = YES;
}
}
@ -130,7 +130,7 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
if (!self.keyboardIsVisible) {
return CGRectNull;
}
return self.keyboardView.frame;
}
@ -141,14 +141,14 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
if (self.textView.inputAccessoryView == nil) {
self.textView.inputAccessoryView = [[UIView alloc] init];
}
[self jsq_registerForNotifications];
}
- (void)endListeningForKeyboard
{
[self jsq_unregisterForNotifications];
[self jsq_setKeyboardViewHidden:NO];
self.keyboardView = nil;
}
@ -158,22 +158,22 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
- (void)jsq_registerForNotifications
{
[self jsq_unregisterForNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveKeyboardDidShowNotification:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveKeyboardWillChangeFrameNotification:)
name:UIKeyboardWillChangeFrameNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveKeyboardDidChangeFrameNotification:)
name:UIKeyboardDidChangeFrameNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(jsq_didReceiveKeyboardDidHideNotification:)
name:UIKeyboardDidHideNotification
@ -189,7 +189,7 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
{
self.keyboardView = self.textView.inputAccessoryView.superview;
[self jsq_setKeyboardViewHidden:NO];
[self jsq_handleKeyboardNotification:notification completion:^(BOOL finished) {
[self.panGestureRecognizer addTarget:self action:@selector(jsq_handlePanGestureRecognizer:)];
}];
@ -203,36 +203,38 @@ 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
@ -257,37 +259,31 @@ 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;
}
CGRect keyboardEndFrameConverted = [self.contextView convertRect:newKeyboardFrame
fromView:self.keyboardView.superview];
[self jsq_notifyKeyboardFrameNotificationForFrame:keyboardEndFrameConverted];
// 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];
}
}
}
@ -297,14 +293,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;
}
@ -313,41 +309,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
@ -357,23 +353,22 @@ 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
@ -382,14 +377,16 @@ typedef void (^JSQAnimationCompletionBlock)(BOOL finished);
}
completion:^(BOOL finished) {
self.keyboardView.userInteractionEnabled = !shouldHide;
if (shouldHide) {
[self jsq_resetKeyboardAndTextView];
[self jsq_setKeyboardViewHidden:YES];
[self jsq_removeKeyboardFrameObserver];
[self.textView resignFirstResponder];
}
}];
}
break;
default:
break;
}

View File

@ -21,7 +21,6 @@
#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
@ -34,43 +33,40 @@
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 does not have to be unique. This value must not be `nil`.
* @discussion This value must not be `nil`. The default value is `@"JSQDefaultSender"`.
*/
@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`.
* checked against this identifier.
* This value must not be `nil`. The default value is `@"JSQDefaultSender"`.
*/
@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;
@ -82,7 +78,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.
@ -94,7 +90,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.
@ -199,7 +195,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,
@ -210,7 +206,7 @@
/**
* Creates and returns a new `JSQMessagesViewController` object.
*
*
* @discussion This is the designated initializer for programmatic instantiation.
*
* @return An initialized `JSQMessagesViewController` object if successful, `nil` otherwise.

View File

@ -18,6 +18,8 @@
#import "JSQMessagesViewController.h"
#import "JSQMessagesKeyboardController.h"
#import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h"
#import "JSQMessageData.h"
@ -39,7 +41,6 @@
#import "NSString+JSQMessages.h"
#import "UIColor+JSQMessages.h"
#import "UIDevice+JSQMessages.h"
#import "NSBundle+JSQMessages.h"
#import "JSQCall.h"
#import "JSQCallCollectionViewCell.h"
@ -62,6 +63,8 @@ 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;
@ -107,13 +110,13 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([JSQMessagesViewController class])
bundle:[NSBundle bundleForClass:[JSQMessagesViewController class]]];
bundle:[NSBundle mainBundle]];
}
+ (instancetype)messagesViewController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([JSQMessagesViewController class])
bundle:[NSBundle bundleForClass:[JSQMessagesViewController class]]];
bundle:[NSBundle mainBundle]];
}
#pragma mark - Initialization
@ -121,23 +124,27 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (void)jsq_configureMessagesViewController
{
self.view.backgroundColor = [UIColor whiteColor];
;
self.jsq_isObserving = NO;
self.toolbarHeightConstraint.constant = self.inputToolbar.preferredDefaultHeight;
self.toolbarHeightConstraint.constant = kJSQMessagesInputToolbarHeightDefault;
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
self.inputToolbar.delegate = self;
self.inputToolbar.contentView.textView.placeHolder = [NSBundle jsq_localizedStringForKey:@"new_message"];
self.inputToolbar.contentView.textView.placeHolder = NSLocalizedStringFromTable(@"New Message", @"JSQMessages", @"Placeholder text for the message input text view");
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];
@ -146,13 +153,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
@ -163,23 +170,20 @@ 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;
}
@ -191,7 +195,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (_showTypingIndicator == showTypingIndicator) {
return;
}
_showTypingIndicator = showTypingIndicator;
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView.collectionViewLayout invalidateLayout];
@ -202,7 +206,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (_showLoadEarlierMessagesHeader == showLoadEarlierMessagesHeader) {
return;
}
_showLoadEarlierMessagesHeader = showLoadEarlierMessagesHeader;
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView.collectionViewLayout invalidateLayout];
@ -220,7 +224,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (void)viewDidLoad
{
[super viewDidLoad];
[[[self class] nib] instantiateWithOwner:self options:nil];
[self jsq_configureMessagesViewController];
@ -229,20 +233,17 @@ 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];
}
@ -252,7 +253,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
[self jsq_addObservers];
[self jsq_addActionToInteractivePopGestureRecognizer:YES];
[self.keyboardController beginListeningForKeyboard];
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
[self.snapshotView removeFromSuperview];
}
@ -299,16 +300,6 @@ 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
@ -331,18 +322,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];
}
@ -354,13 +345,13 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
}
- (void)finishReceivingMessageAnimated:(BOOL)animated {
self.showTypingIndicator = NO;
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
[self.collectionView reloadData];
if (self.automaticallyScrollsToMostRecentMessage && ![self jsq_isMenuVisible]) {
if (self.automaticallyScrollsToMostRecentMessage) {
[self scrollToBottomAnimated:animated];
}
}
@ -370,16 +361,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 < CGRectGetHeight(self.collectionView.bounds));
BOOL isContentTooSmall = (collectionViewContentHeight < self.collectionView.bounds.size.height);
if (isContentTooSmall) {
// workaround for the first few messages not scrolling
// when the collection view content size is too small, `scrollToItemAtIndexPath:` doesn't work properly
@ -388,18 +379,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];
@ -456,12 +447,11 @@ 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;
@ -473,7 +463,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
isMediaMessage = [messageItem isMediaMessage];
}
NSString *cellIdentifier = nil;
if (isCall) {
@ -559,22 +549,14 @@ 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];
@ -586,7 +568,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;
@ -594,12 +576,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];
@ -611,25 +593,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.backgroundColor = [UIColor clearColor];
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
return cell;
}
@ -643,7 +625,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
else if (self.showLoadEarlierMessagesHeader && [kind isEqualToString:UICollectionElementKindSectionHeader]) {
return [collectionView dequeueLoadEarlierMessagesViewHeaderForIndexPath:indexPath];
}
return nil;
}
@ -653,7 +635,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (!self.showTypingIndicator) {
return CGSizeZero;
}
return CGSizeMake([collectionViewLayout itemWidth], kJSQMessagesTypingIndicatorFooterViewHeight);
}
@ -663,7 +645,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (!self.showLoadEarlierMessagesHeader) {
return CGSizeZero;
}
return CGSizeMake([collectionViewLayout itemWidth], kJSQMessagesLoadEarlierHeaderViewHeight);
}
@ -684,16 +666,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;
}
@ -702,14 +684,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 = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
id<JSQMessageData> messageData = [self collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
[[UIPasteboard generalPasteboard] setString:[messageData text]];
}
}
@ -725,19 +707,19 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath
{
return 0.0f;
return 1.0f;
}
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath
{
return 0.0f;
return 1.0f;
}
- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath
{
return 0.0f;
return 1.0f;
}
- (void)collectionView:(JSQMessagesCollectionView *)collectionView
@ -784,10 +766,9 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (NSString *)jsq_currentlyComposedMessageText
{
// 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];
// add a space to accept any auto-correct suggestions
NSString *text = self.inputToolbar.contentView.textView.text;
self.inputToolbar.contentView.textView.text = [text stringByAppendingString:@" "];
return [self.inputToolbar.contentView.textView.text jsq_stringByTrimingWhitespace];
}
@ -798,9 +779,9 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (textView != self.inputToolbar.contentView.textView) {
return;
}
[textView becomeFirstResponder];
if (self.automaticallyScrollsToMostRecentMessage) {
[self scrollToBottomAnimated:YES];
}
@ -811,7 +792,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (textView != self.inputToolbar.contentView.textView) {
return;
}
[self.inputToolbar toggleSendButtonEnabled];
}
@ -820,7 +801,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
if (textView != self.inputToolbar.contentView.textView) {
return;
}
[textView resignFirstResponder];
}
@ -838,20 +819,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
@ -863,7 +844,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];
@ -876,15 +857,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) {
@ -901,20 +882,30 @@ 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];
}
- (void)jsq_setToolbarBottomLayoutGuideConstant:(CGFloat)constant
{
self.toolbarBottomLayoutGuide.constant = constant;
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
[self jsq_updateCollectionViewInsets];
}
@ -933,16 +924,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;
@ -955,7 +946,7 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateFailed:
[self.keyboardController beginListeningForKeyboard];
if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
[self.snapshotView removeFromSuperview];
}
@ -969,35 +960,35 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (BOOL)jsq_inputToolbarHasReachedMaximumHeight
{
return CGRectGetMinY(self.inputToolbar.frame) == (self.topLayoutGuide.length + self.topContentAdditionalInset);
return (CGRectGetMinY(self.inputToolbar.frame) == self.topLayoutGuide.length);
}
- (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 + self.topContentAdditionalInset) {
dy = toolbarOriginY - (self.topLayoutGuide.length + self.topContentAdditionalInset);
if (newToolbarOriginY <= self.topLayoutGuide.length) {
dy = toolbarOriginY - self.topLayoutGuide.length;
[self jsq_scrollComposerTextViewToBottomAnimated:YES];
}
[self jsq_adjustInputToolbarHeightConstraintByDelta:dy];
[self jsq_updateKeyboardTriggerPoint];
if (dy < 0) {
[self jsq_scrollComposerTextViewToBottomAnimated:NO];
}
@ -1005,31 +996,26 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
- (void)jsq_adjustInputToolbarHeightConstraintByDelta:(CGFloat)dy
{
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.toolbarHeightConstraint.constant += dy;
if (self.toolbarHeightConstraint.constant < kJSQMessagesInputToolbarHeightDefault) {
self.toolbarHeightConstraint.constant = kJSQMessagesInputToolbarHeightDefault;
}
[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
@ -1068,12 +1054,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;
}
@ -1082,14 +1068,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;
}
@ -1100,12 +1086,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
@ -1115,11 +1101,11 @@ static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObserv
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidChangeStatusBarFrameNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerWillShowMenuNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerWillHideMenuNotification
object:nil];

View File

@ -24,7 +24,7 @@
@interface JSQMessagesAvatarImageFactory ()
+ (UIImage *)jsq_circularImage:(UIImage *)image
withDiameter:(NSUInteger)diameter
withDiamter:(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
withDiameter:diameter
withDiamter: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
withDiameter:diameter
withDiamter:diameter
highlightedColor:nil];
}
+ (UIImage *)circularAvatarHighlightedImage:(UIImage *)image withDiameter:(NSUInteger)diameter
{
return [JSQMessagesAvatarImageFactory jsq_circularImage:image
withDiameter:diameter
withDiamter: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
withDiameter:diameter
withDiamter:diameter
highlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]];
return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatarImage
highlightedImage:avatarHighlightedImage
placeholderImage:avatarImage];
@ -108,64 +108,70 @@
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 = [initials boundingRectWithSize:frame.size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];
CGRect textFrame = [text 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);
[initials drawAtPoint:drawPoint withAttributes:attributes];
[text drawAtPoint:drawPoint withAttributes:attributes];
image = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(context);
}
UIGraphicsEndImageContext();
return [JSQMessagesAvatarImageFactory jsq_circularImage:image withDiameter:diameter highlightedColor:nil];
return [JSQMessagesAvatarImageFactory jsq_circularImage:image withDiamter:diameter highlightedColor:nil];
}
+ (UIImage *)jsq_circularImage:(UIImage *)image withDiameter:(NSUInteger)diameter highlightedColor:(UIColor *)highlightedColor
+ (UIImage *)jsq_circularImage:(UIImage *)image withDiamter:(NSUInteger)diameter highlightedColor:(UIColor *)highlightedColor
{
NSParameterAssert(image != nil);
NSParameterAssert(diameter > 0);
CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter);
UIImage *newImage = nil;
UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
UIBezierPath *imgPath = [UIBezierPath bezierPathWithOvalInRect:frame];
[imgPath addClip];
[image drawInRect:frame];
if (highlightedColor != nil) {
CGContextSetFillColorWithColor(context, highlightedColor.CGColor);
CGContextFillEllipseInRect(context, frame);
}
newImage = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(context);
}
UIGraphicsEndImageContext();

View File

@ -20,7 +20,6 @@
#import "UIColor+JSQMessages.h"
#import "UIImage+JSQMessages.h"
#import "NSBundle+JSQMessages.h"
@implementation JSQMessagesToolbarButtonFactory
@ -30,22 +29,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 = [NSBundle jsq_localizedStringForKey:@"send"];
NSString *sendTitle = NSLocalizedStringFromTable(@"Send", @"JSQMessages", @"Text for the send button on the messages view toolbar");
UIButton *sendButton = [[UIButton alloc] initWithFrame:CGRectZero];
[sendButton setTitle:sendTitle forState:UIControlStateNormal];
[sendButton setTitleColor:[UIColor jsq_messageBubbleBlueColor] forState:UIControlStateNormal];
@ -58,19 +57,19 @@
sendButton.contentMode = UIViewContentModeCenter;
sendButton.backgroundColor = [UIColor clearColor];
sendButton.tintColor = [UIColor jsq_messageBubbleBlueColor];
CGFloat maxHeight = 32.0f;
CGRect sendTitleRect = [sendTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, maxHeight)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{ NSFontAttributeName : sendButton.titleLabel.font }
context:nil];
sendButton.frame = CGRectMake(0.0f,
0.0f,
CGRectGetWidth(CGRectIntegral(sendTitleRect)),
maxHeight);
return sendButton;
}

View File

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

View File

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

View File

@ -80,8 +80,6 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
@implementation JSQMessagesCollectionViewFlowLayout
@dynamic collectionView;
#pragma mark - Initialization
- (void)jsq_configureFlowLayout
@ -292,7 +290,7 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
[self jsq_resetDynamicAnimator];
}
if (context.invalidateFlowLayoutMessagesCache) {
if (context.emptyCache) {
[self jsq_resetLayout];
}
@ -444,7 +442,7 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
{
id<JSQMessageData> messageItem = [self.collectionView.dataSource collectionView:self.collectionView messageDataForItemAtIndexPath:indexPath];
NSValue *cachedSize = [self.messageBubbleCache objectForKey:@([messageItem messageHash])];
NSValue *cachedSize = [self.messageBubbleCache objectForKey:@(messageItem.hash)];
if (cachedSize != nil) {
return [cachedSize CGSizeValue];
}
@ -493,7 +491,7 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
finalSize = CGSizeMake(kDisplayedMessageCellWidth, kDisplayedMessageCellHeight);
}
[self.messageBubbleCache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageItem messageHash])];
[self.messageBubbleCache setObject:[NSValue valueWithCGSize:finalSize] forKey:@(messageItem.hash)];
return finalSize;
}
@ -558,11 +556,6 @@ 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;
@ -614,7 +607,7 @@ const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f;
// if touch is not (0,0) -- adjust item center "in flight"
if (!CGPointEqualToPoint(CGPointZero, touchLocation)) {
CGFloat distanceFromTouch = fabs(touchLocation.y - springBehavior.anchorPoint.y);
CGFloat distanceFromTouch = fabsf(touchLocation.y - springBehavior.anchorPoint.y);
CGFloat scrollResistance = distanceFromTouch / self.springResistanceFactor;
if (self.latestDelta < 0.0f) {

View File

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

View File

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

View File

@ -85,12 +85,20 @@
- (CGSize)jsq_correctedAvatarSizeFromSize:(CGSize)size
{
return CGSizeMake(ceilf(size.width), ceilf(size.height));
// 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);
}
- (CGFloat)jsq_correctedLabelHeightForHeight:(CGFloat)height
{
return ceilf(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);
}
#pragma mark - NSObject

View File

@ -54,21 +54,15 @@ 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
displayString:(NSString*)detailString;
status:(CallStatus)status;
-(NSString*)text;
-(NSString*)dateText;
-(UIImage*)thumbnailImage;

View File

@ -18,7 +18,6 @@
callerDisplayName:(NSString *)senderDisplayName
date:(NSDate *)date
status:(CallStatus)status
displayString:(NSString *)detailString
{
NSParameterAssert(senderId != nil);
NSParameterAssert(senderDisplayName != nil);
@ -30,7 +29,6 @@
_date = [date copy];
_status = status;
_messageType = TSCallAdapter;
_detailString = [detailString stringByAppendingFormat:@" "];
}
return self;
@ -38,7 +36,7 @@
-(id)init
{
NSAssert(NO,@"%s is not a valid initializer for %@. Use %@ instead", __PRETTY_FUNCTION__, [self class], NSStringFromSelector(@selector(initWithCallerId:callerDisplayName:date:status:displayString:)));
NSAssert(NO,@"%s is not a valid initializer for %@. Use %@ instead", __PRETTY_FUNCTION__, [self class], NSStringFromSelector(@selector(initWithCallerId:callerDisplayName:date:status:)));
return nil;
}
@ -49,6 +47,23 @@
_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];
@ -103,9 +118,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
@ -148,16 +163,9 @@
return [[[self class] allocWithZone:zone]initWithCallerId:self.senderId
callerDisplayName:self.senderDisplayName
date:self.date
status:self.status
displayString:self.detailString];
status:self.status];
}
- (NSUInteger)messageHash{
return self.hash;
}
- (NSString *)text{
return _detailString;
}
@end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -75,11 +75,8 @@ 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)messageHash;
- (NSUInteger)hash;
@optional

View File

@ -72,10 +72,7 @@
/**
* @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)mediaHash;
- (NSUInteger)hash;
@end

View File

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

View File

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

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6254" systemVersion="14C109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6250" systemVersion="14C68k" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6244"/>
</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="0.0" width="35" height="35"/>
<rect key="frame" x="143" y="-3" 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="-15" id="4ie-2d-9cn"/>
<constraint firstItem="OVa-Xw-5vl" firstAttribute="top" secondItem="ePO-Cy-jUE" secondAttribute="bottom" constant="-12" id="4ie-2d-9cn"/>
<constraint firstAttribute="trailing" secondItem="OVa-Xw-5vl" secondAttribute="trailing" constant="8" id="5Mk-DE-t1M"/>
<constraint firstItem="OVa-Xw-5vl" firstAttribute="leading" secondItem="eMU-z2-CzM" secondAttribute="leading" constant="8" id="E3N-dw-XQG"/>
<constraint firstItem="OVa-Xw-5vl" firstAttribute="top" secondItem="ePO-Cy-jUE" secondAttribute="bottom" id="Yy0-Kd-Lu4"/>

View File

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

View File

@ -60,24 +60,12 @@
*/
- (void)messagesCollectionViewCellDidTapCell:(JSQMessagesCollectionViewCell *)cell atPosition:(CGPoint)position;
/**
* Tells the delegate that an actions has been selected from the menu of this cell.
* This method is automatically called for any registered actions.
*
* @param cell The cell that displayed the menu.
* @param action The action that has been performed.
* @param sender The object that initiated the action.
*
* @see `JSQMessagesCollectionViewCell`
*/
- (void)messagesCollectionViewCell:(JSQMessagesCollectionViewCell *)cell didPerformAction:(SEL)action withSender:(id)sender;
@end
/**
* The `JSQMessagesCollectionViewCell` is an abstract base class that presents the content for
* a single message data item when that item is within the collection views visible bounds.
* The `JSQMessagesCollectionViewCell` is an abstract base class that presents the content for
* a single message data item when that item is within the collection views visible bounds.
* The layout and presentation of cells is managed by the collection view and its corresponding layout object.
*
* @warning This class is intended to be subclassed. You should not use it directly.
@ -118,7 +106,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`.
*/
@ -132,7 +120,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;
@ -143,7 +131,8 @@
@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:`
@ -172,7 +161,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;
@ -191,16 +180,4 @@
*/
+ (NSString *)mediaCellReuseIdentifier;
/**
* Registers an action to be available in the cell's menu.
*
* @param action The selector to register with the cell.
*
* @discussion Non-standard or non-system actions must be added to the `UIMenuController` manually.
* You can do this by creating a new `UIMenuItem` and adding it via the controller's `menuItems` property.
*
* @warning Note that all message cells share the all actions registered here.
*/
+ (void)registerMenuAction:(SEL)action;
@end

View File

@ -27,9 +27,6 @@
#import "UIColor+JSQMessages.h"
static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
@interface JSQMessagesCollectionViewCell ()
@property (weak, nonatomic) IBOutlet JSQMessagesLabel *cellTopLabel;
@ -70,21 +67,14 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
@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 bundleForClass:[self class]]];
return [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
}
+ (NSString *)cellReuseIdentifier
@ -97,36 +87,31 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
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;
@ -135,17 +120,17 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
- (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;
}
@ -155,52 +140,47 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
- (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;
}
@ -230,57 +210,25 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
- (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;
}
@ -290,7 +238,7 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
if (CGSizeEqualToSize(avatarViewSize, self.avatarViewSize)) {
return;
}
[self jsq_updateConstraint:self.avatarContainerViewWidthConstraint withConstant:avatarViewSize.width];
[self jsq_updateConstraint:self.avatarContainerViewHeightConstraint withConstant:avatarViewSize.height];
}
@ -300,7 +248,7 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
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];
@ -309,16 +257,20 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
- (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
@ -354,7 +306,7 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
if (constraint.constant == constant) {
return;
}
constraint.constant = constant;
}
@ -363,7 +315,7 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
- (void)jsq_handleTapGesture:(UITapGestureRecognizer *)tap
{
CGPoint touchPt = [tap locationInView:self];
if (CGRectContainsPoint(self.avatarContainerView.frame, touchPt)) {
[self.delegate messagesCollectionViewCellDidTapAvatar:self];
}
@ -378,7 +330,7 @@ static NSMutableSet *jsqMessagesCollectionViewCellActions = nil;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
CGPoint touchPt = [touch locationInView:self];
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
return CGRectContainsPoint(self.messageBubbleContainerView.frame, touchPt);
}

View File

@ -19,10 +19,14 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "JSQMessagesToolbarContentView.h"
@class JSQMessagesInputToolbar;
#import "JSQMessagesToolbarContentView.h"
/**
* A constant the specifies the default height for a `JSQMessagesInputToolbar`.
*/
FOUNDATION_EXPORT const CGFloat kJSQMessagesInputToolbarHeightDefault;
/**
* The `JSQMessagesInputToolbarDelegate` protocol defines methods for interacting with
@ -55,7 +59,8 @@
/**
* 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
@ -71,7 +76,7 @@
/**
* 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.
@ -82,29 +87,10 @@
*/
@property (assign, nonatomic) BOOL sendButtonOnRight;
/**
* Specifies the default (minimum) height for the toolbar. The default value is `44.0f`. This value must be positive.
*/
@property (assign, nonatomic) CGFloat preferredDefaultHeight;
/**
* Specifies the maximum height for the toolbar. The default value is `NSNotFound`, which specifies no maximum height.
*/
@property (assign, nonatomic) NSUInteger maximumHeight;
/**
* Enables or disables the send button based on whether or not its `textView` has text.
* That is, the send button will be enabled if there is text in the `textView`, and disabled otherwise.
*/
- (void)toggleSendButtonEnabled;
/**
* Loads the content view for the toolbar.
*
* @discussion Override this method to provide a custom content view for the toolbar.
*
* @return An initialized `JSQMessagesToolbarContentView` if successful, otherwise `nil`.
*/
- (JSQMessagesToolbarContentView *)loadToolbarContentView;
@end

View File

@ -26,6 +26,8 @@
#import "UIImage+JSQMessages.h"
#import "UIView+JSQMessages.h"
const CGFloat kJSQMessagesInputToolbarHeightDefault = 44.0f;
static void * kJSQMessagesInputToolbarKeyValueObservingContext = &kJSQMessagesInputToolbarKeyValueObservingContext;
@ -45,58 +47,38 @@ 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;
self.preferredDefaultHeight = 44.0f;
self.maximumHeight = NSNotFound;
JSQMessagesToolbarContentView *toolbarContentView = [self loadToolbarContentView];
NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([JSQMessagesToolbarContentView class]) owner:nil options:nil];
JSQMessagesToolbarContentView *toolbarContentView = [nibViews firstObject];
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
@ -129,28 +111,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];
}
}
@ -161,17 +143,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;
}
@ -180,12 +162,12 @@ static void * kJSQMessagesInputToolbarKeyValueObservingContext = &kJSQMessagesIn
if (!_jsq_isObserving) {
return;
}
@try {
[_contentView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(leftBarButtonItem))
context:kJSQMessagesInputToolbarKeyValueObservingContext];
[_contentView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(rightBarButtonItem))
context:kJSQMessagesInputToolbarKeyValueObservingContext];

View File

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

View File

@ -47,7 +47,7 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([JSQMessagesToolbarContentView class])
bundle:[NSBundle bundleForClass:[JSQMessagesToolbarContentView class]]];
bundle:[NSBundle mainBundle]];
}
#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,21 +95,19 @@ 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;
}
@ -124,7 +122,7 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
if (_rightBarButtonItem) {
[_rightBarButtonItem removeFromSuperview];
}
if (!rightBarButtonItem) {
_rightBarButtonItem = nil;
self.rightHorizontalSpacingConstraint.constant = 0.0f;
@ -132,21 +130,19 @@ const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f;
self.rightBarButtonContainerView.hidden = YES;
return;
}
if (CGRectEqualToRect(rightBarButtonItem.frame, CGRectZero)) {
rightBarButtonItem.frame = self.rightBarButtonContainerView.bounds;
}
self.rightBarButtonContainerView.hidden = NO;
self.rightHorizontalSpacingConstraint.constant = kJSQMessagesToolbarContentViewHorizontalSpacingDefault;
self.rightBarButtonItemWidth = CGRectGetWidth(rightBarButtonItem.frame);
[rightBarButtonItem setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.rightBarButtonContainerView addSubview:rightBarButtonItem];
[self.rightBarButtonContainerView jsq_pinAllEdgesOfSubview:rightBarButtonItem];
[self setNeedsUpdateConstraints];
_rightBarButtonItem = rightBarButtonItem;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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