Repository: jessesquires/JSQMessagesViewController Branch: develop Commit: 9343b8fc8c1c Files: 248 Total size: 911.7 KB Directory structure: gitextract_skmjj783/ ├── .cocoadocs.yml ├── .codecov.yml ├── .github/ │ ├── CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── Assets/ │ ├── jsq_messages_banner.psd │ ├── jsq_messages_icon.psd │ ├── jsq_messages_splash.psd │ └── jsq_messages_splash_ipad.psd ├── CHANGELOG.md ├── Documentation/ │ ├── apps_using_this_library.md │ ├── contributor_onboarding.md │ ├── faq.md │ ├── getting_started.md │ └── migration.md ├── JSQMessages.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── xcshareddata/ │ └── xcschemes/ │ └── JSQMessages.xcscheme ├── JSQMessages.xcworkspace/ │ └── contents.xcworkspacedata ├── JSQMessagesDemo/ │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj/ │ │ ├── Localizable.strings │ │ └── Main.storyboard │ ├── DemoMessagesViewController.h │ ├── DemoMessagesViewController.m │ ├── DemoModelData.h │ ├── DemoModelData.m │ ├── DemoSettingsViewController.h │ ├── DemoSettingsViewController.m │ ├── Images.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── DemoAvatars/ │ │ │ ├── demo_avatar_cook.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── demo_avatar_jobs.imageset/ │ │ │ │ └── Contents.json │ │ │ └── demo_avatar_woz.imageset/ │ │ │ └── Contents.json │ │ ├── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ └── goldengate.imageset/ │ │ └── Contents.json │ ├── Info.plist │ ├── NSUserDefaults+DemoSettings.h │ ├── NSUserDefaults+DemoSettings.m │ ├── TableViewController.h │ ├── TableViewController.m │ ├── he.lproj/ │ │ ├── Localizable.strings │ │ └── Main.strings │ ├── jsq_messages_sample.m4a │ └── main.m ├── JSQMessagesTests/ │ ├── CategoryTests/ │ │ ├── JSQMessagesNSBundleTests.m │ │ ├── JSQMessagesNSStringTests.m │ │ ├── JSQMessagesUIColorTests.m │ │ ├── JSQMessagesUIImageTests.m │ │ └── JSQMessagesUIViewTests.m │ ├── ControllerTests/ │ │ └── JSQMessagesViewControllerTests.m │ ├── FactoryTests/ │ │ ├── JSQMessagesAvatarImageFactoryTests.m │ │ ├── JSQMessagesBubbleImageFactoryTests.m │ │ ├── JSQMessagesMediaViewBubbleImageMaskerTests.m │ │ ├── JSQMessagesTimestampFormatterTests.m │ │ └── JSQMessagesToolbarButtonFactoryTests.m │ ├── Info.plist │ ├── LayoutTests/ │ │ ├── JSQMessagesCollectionViewFlowLayoutTests.m │ │ └── JSQMessagesCollectionViewLayoutAttributesTests.m │ ├── ModelTests/ │ │ ├── JSQAudioMediaItemTests.m │ │ ├── JSQLocationMediaItemTests.m │ │ ├── JSQMessageMediaTests.m │ │ ├── JSQMessageTextTests.m │ │ ├── JSQMessagesAvatarImageTests.m │ │ ├── JSQMessagesBubbleImageTests.m │ │ ├── JSQPhotoMediaItemTests.m │ │ └── JSQVideoMediaItemTests.m │ └── ViewTests/ │ ├── JSQMessagesCollectionViewCellTests.m │ ├── JSQMessagesCollectionViewTests.m │ ├── JSQMessagesComposerTextViewTests.m │ ├── JSQMessagesInputToolbarTests.m │ ├── JSQMessagesLabelTests.m │ ├── JSQMessagesLoadEarlierHeaderViewTests.m │ ├── JSQMessagesToolbarContentViewTests.m │ └── JSQMessagesTypingIndicatorFooterViewTests.m ├── JSQMessagesViewController/ │ ├── Assets/ │ │ └── JSQMessagesAssets.bundle/ │ │ ├── Base.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── Sounds/ │ │ │ ├── message_received.aiff │ │ │ └── message_sent.aiff │ │ ├── ar.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── bs.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── cs.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── da.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── de.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── en.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── es.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── fa.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── fi.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── fr.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── he.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── hr.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── id.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── it.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── ja.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── ko.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── ms.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── nb.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── nl.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── pl.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── pt.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── ro.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── ru.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── sv.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── th.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── tr.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── vi.lproj/ │ │ │ └── JSQMessages.strings │ │ ├── zh-Hans.lproj/ │ │ │ └── JSQMessages.strings │ │ └── zh-Hant.lproj/ │ │ └── JSQMessages.strings │ ├── Categories/ │ │ ├── NSBundle+JSQMessages.h │ │ ├── NSBundle+JSQMessages.m │ │ ├── NSString+JSQMessages.h │ │ ├── NSString+JSQMessages.m │ │ ├── UIColor+JSQMessages.h │ │ ├── UIColor+JSQMessages.m │ │ ├── UIImage+JSQMessages.h │ │ ├── UIImage+JSQMessages.m │ │ ├── UIView+JSQMessages.h │ │ └── UIView+JSQMessages.m │ ├── Controllers/ │ │ ├── JSQMessagesViewController.h │ │ ├── JSQMessagesViewController.m │ │ └── JSQMessagesViewController.xib │ ├── Factories/ │ │ ├── JSQMessagesAvatarImageFactory.h │ │ ├── JSQMessagesAvatarImageFactory.m │ │ ├── JSQMessagesBubbleImageFactory.h │ │ ├── JSQMessagesBubbleImageFactory.m │ │ ├── JSQMessagesMediaViewBubbleImageMasker.h │ │ ├── JSQMessagesMediaViewBubbleImageMasker.m │ │ ├── JSQMessagesTimestampFormatter.h │ │ ├── JSQMessagesTimestampFormatter.m │ │ ├── JSQMessagesToolbarButtonFactory.h │ │ ├── JSQMessagesToolbarButtonFactory.m │ │ ├── JSQMessagesVideoThumbnailFactory.h │ │ └── JSQMessagesVideoThumbnailFactory.m │ ├── JSQMessages.h │ ├── Layout/ │ │ ├── JSQAudioMediaViewAttributes.h │ │ ├── JSQAudioMediaViewAttributes.m │ │ ├── JSQMessagesBubbleSizeCalculating.h │ │ ├── JSQMessagesBubblesSizeCalculator.h │ │ ├── JSQMessagesBubblesSizeCalculator.m │ │ ├── JSQMessagesCollectionViewFlowLayout.h │ │ ├── JSQMessagesCollectionViewFlowLayout.m │ │ ├── JSQMessagesCollectionViewFlowLayoutInvalidationContext.h │ │ ├── JSQMessagesCollectionViewFlowLayoutInvalidationContext.m │ │ ├── JSQMessagesCollectionViewLayoutAttributes.h │ │ └── JSQMessagesCollectionViewLayoutAttributes.m │ ├── Model/ │ │ ├── JSQAudioMediaItem.h │ │ ├── JSQAudioMediaItem.m │ │ ├── JSQLocationMediaItem.h │ │ ├── JSQLocationMediaItem.m │ │ ├── JSQMediaItem.h │ │ ├── JSQMediaItem.m │ │ ├── JSQMessage.h │ │ ├── JSQMessage.m │ │ ├── JSQMessageAvatarImageDataSource.h │ │ ├── JSQMessageBubbleImageDataSource.h │ │ ├── JSQMessageData.h │ │ ├── JSQMessageMediaData.h │ │ ├── JSQMessagesAvatarImage.h │ │ ├── JSQMessagesAvatarImage.m │ │ ├── JSQMessagesBubbleImage.h │ │ ├── JSQMessagesBubbleImage.m │ │ ├── JSQMessagesCollectionViewDataSource.h │ │ ├── JSQMessagesCollectionViewDelegateFlowLayout.h │ │ ├── JSQMessagesViewAccessoryButtonDelegate.h │ │ ├── JSQPhotoMediaItem.h │ │ ├── JSQPhotoMediaItem.m │ │ ├── JSQVideoMediaItem.h │ │ └── JSQVideoMediaItem.m │ └── Views/ │ ├── JSQMessagesCellTextView.h │ ├── JSQMessagesCellTextView.m │ ├── JSQMessagesCollectionView.h │ ├── JSQMessagesCollectionView.m │ ├── JSQMessagesCollectionViewCell.h │ ├── JSQMessagesCollectionViewCell.m │ ├── JSQMessagesCollectionViewCellIncoming.h │ ├── JSQMessagesCollectionViewCellIncoming.m │ ├── JSQMessagesCollectionViewCellIncoming.xib │ ├── JSQMessagesCollectionViewCellOutgoing.h │ ├── JSQMessagesCollectionViewCellOutgoing.m │ ├── JSQMessagesCollectionViewCellOutgoing.xib │ ├── JSQMessagesComposerTextView.h │ ├── JSQMessagesComposerTextView.m │ ├── JSQMessagesInputToolbar.h │ ├── JSQMessagesInputToolbar.m │ ├── JSQMessagesLabel.h │ ├── JSQMessagesLabel.m │ ├── JSQMessagesLoadEarlierHeaderView.h │ ├── JSQMessagesLoadEarlierHeaderView.m │ ├── JSQMessagesLoadEarlierHeaderView.xib │ ├── JSQMessagesMediaPlaceholderView.h │ ├── JSQMessagesMediaPlaceholderView.m │ ├── JSQMessagesToolbarContentView.h │ ├── JSQMessagesToolbarContentView.m │ ├── JSQMessagesToolbarContentView.xib │ ├── JSQMessagesTypingIndicatorFooterView.h │ ├── JSQMessagesTypingIndicatorFooterView.m │ ├── JSQMessagesTypingIndicatorFooterView.xib │ ├── JSQMessagesTypingView.h │ └── JSQMessagesTypingView.m ├── JSQMessagesViewController.podspec ├── LICENSE ├── README.md └── SwiftExample/ ├── Podfile ├── Pods/ │ ├── Local Podspecs/ │ │ └── JSQMessagesViewController.podspec.json │ ├── Pods.xcodeproj/ │ │ └── project.pbxproj │ └── Target Support Files/ │ ├── JSQMessagesViewController/ │ │ ├── Info.plist │ │ ├── JSQMessagesViewController-dummy.m │ │ ├── JSQMessagesViewController-prefix.pch │ │ ├── JSQMessagesViewController-umbrella.h │ │ ├── JSQMessagesViewController.modulemap │ │ └── JSQMessagesViewController.xcconfig │ ├── Pods-SwiftExample/ │ │ ├── Info.plist │ │ ├── Pods-SwiftExample-acknowledgements.markdown │ │ ├── Pods-SwiftExample-acknowledgements.plist │ │ ├── Pods-SwiftExample-dummy.m │ │ ├── Pods-SwiftExample-frameworks.sh │ │ ├── Pods-SwiftExample-resources.sh │ │ ├── Pods-SwiftExample-umbrella.h │ │ ├── Pods-SwiftExample.debug.xcconfig │ │ ├── Pods-SwiftExample.modulemap │ │ └── Pods-SwiftExample.release.xcconfig │ └── Pods-SwiftExampleTests/ │ ├── Info.plist │ ├── Pods-SwiftExampleTests-acknowledgements.markdown │ ├── Pods-SwiftExampleTests-acknowledgements.plist │ ├── Pods-SwiftExampleTests-dummy.m │ ├── Pods-SwiftExampleTests-frameworks.sh │ ├── Pods-SwiftExampleTests-resources.sh │ ├── Pods-SwiftExampleTests-umbrella.h │ ├── Pods-SwiftExampleTests.debug.xcconfig │ ├── Pods-SwiftExampleTests.modulemap │ └── Pods-SwiftExampleTests.release.xcconfig ├── SwiftExample/ │ ├── AppDelegate.swift │ ├── Base.lproj/ │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── ChatViewController.swift │ ├── Conversation.swift │ ├── DemoConversation.swift │ ├── Info.plist │ ├── InitalTableViewController.swift │ └── SettingsTableViewController.swift ├── SwiftExample.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ └── contents.xcworkspacedata ├── SwiftExample.xcworkspace/ │ └── contents.xcworkspacedata └── SwiftExampleTests/ ├── ChatViewControllerTests.swift ├── Info.plist └── SettingsTests.swift ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cocoadocs.yml ================================================ highlight-color: "#43D15B" highlight-dark-color: "#319942" darker-color: "#8A998C" darker-dark-color: "#454D46" background-color: "#D7E5CF" alt-link-color: "#00A81C" warning-color: "#0FB82B" ================================================ FILE: .codecov.yml ================================================ codecov: branch: develop coverage: precision: 2 round: nearest range: "60...100" ignore: - JSQMessagesDemo/* - Pods/* - JSQMessagesTests/* status: project: default: target: auto threshold: 2.0 branches: - master - develop patch: default: target: auto branches: - master - develop comment: layout: header, diff, changes, sunburst, uncovered behavior: default branches: - master - develop ================================================ FILE: .github/CONDUCT.md ================================================ # JSQMessagesViewController Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at **jesse.squires.developer [at] gmail [dot] com**. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing to JSQMessagesViewController ## Code of Conduct Please read our [Code of Conduct](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/CONDUCT.md). Intolerance, disrespect, harassment, and any of form of negativity will not be tolerated. ## Opening a new issue 1. Read *all* of the [`README`](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/README.md) :speak_no_evil: * Search [open issues](https://github.com/jessesquires/JSQMessagesViewController/issues) *and* [closed issues](https://github.com/jessesquires/JSQMessagesViewController/issues?q=is%3Aissue+is%3Aclosed) to **avoid opening a duplicate issue!** :see_no_evil: * If your issue exists, please comment on its thread with your new information :hear_no_evil: * Otherwise, open a new issue with a good title and description :memo: * Provide **all** of the following information: - Library version(s) :octocat: - iOS version(s) :iphone: - Devices/Simulators affected :iphone: - Expected behavior vs actual behavior - Complete steps to reproduce the issue :warning: - Link to a project that exhibits the issue, if possible fork the repo and modify the provided demo project :construction: - Screenshots/GIFs/Videos showing the issue, if applicable :camera: - Full crash log, if applicable :boom: - Search for and list any issues that might be related :mag_right: ## Submitting a pull request 1. Link to the issue that the pull request resolves. If there isn't one, create one. 2. Write unit tests that test your changes, if applicable. 3. Update header docs, if needed. 4. Follow existing coding style, and these [style guidelines](https://github.com/jessesquires/HowToContribute#style-guidelines). 5. Resolve any merge conflicts. 6. Squash your commits into a single commit. ## Questions and help See the [Questions & Help](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/README.md#questions--help), and [Documentation](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/README.md#documentation) sections in the `README`. ## General guidelines Please also read through these more general [contribution guidelines](https://github.com/jessesquires/HowToContribute). ## Did you read all of this? You even followed the links? Congratulations! You deserve a high-five. :tada: ![img](http://media.giphy.com/media/LdnaND03GRE9q/giphy.gif) ### New issue checklist Now show me how awesome you are! :smile: When opening your new issue and filling out the checklist, you'll be asked for confirmation. Confirm that you've read this with these emoji: :muscle::sunglasses::facepunch: > - [x] I have reviewed the contributing guidelines. Confirmation: :muscle::sunglasses::facepunch: ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ > This library is ⚠️ [deprecated](https://www.jessesquires.com/blog/officially-deprecating-jsqmessagesviewcontroller/) ⚠️ and is **only** accepting pull requests for critical bug fixes. Consider using [MessageKit](https://github.com/MessageKit/MessageKit) for new projects. ## New issue checklist - [ ] I understand that this library is ⚠️ [deprecated](https://www.jessesquires.com/blog/officially-deprecating-jsqmessagesviewcontroller/) ⚠️ and is **only** accepting pull requests for critical bug fixes. - [ ] I have read the [`README`](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/README.md), [documentation](http://cocoadocs.org/docsets/JSQMessagesViewController/), and [FAQ](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/faq.md). - [ ] [Contributing guidelines](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/CONTRIBUTING.md) confirmation: ____ - [ ] I have searched [existing issues](https://github.com/jessesquires/JSQMessagesViewController/issues?q=is%3Aissue+sort%3Acreated-desc) and **this is not a duplicate**. ## General information - `JSQMessagesViewController` version: - iOS version: - Devices/Simulators: - Reproducible in the demo project? (Yes/No): - Any related issues: ## What happened? ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ > This library is ⚠️ [deprecated](https://www.jessesquires.com/blog/officially-deprecating-jsqmessagesviewcontroller/) ⚠️ and is **only** accepting pull requests for critical bug fixes. Consider using [MessageKit](https://github.com/MessageKit/MessageKit) for new projects. ## Pull request checklist - [ ] I understand that this library is ⚠️ [deprecated](https://www.jessesquires.com/blog/officially-deprecating-jsqmessagesviewcontroller/) ⚠️ and is **only** accepting pull requests for critical bug fixes. - [ ] All tests pass. - [ ] Demo project builds and runs. - [ ] I have resolved merge conflicts. - [ ] I have followed the [coding style](https://github.com/jessesquires/HowToContribute#style-guidelines). [Contributing guidelines](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/CONTRIBUTING.md) confirmation: ____ #### This fixes issue # ## What's in this pull request? ================================================ FILE: .gitignore ================================================ .DS_Store # Xcode /build/* */build/* *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata profile *.moved-aside DerivedData .idea/ *.hmap *.xccheckout ================================================ FILE: .travis.yml ================================================ language: objective-c osx_image: xcode8 cache: cocoapods env: global: - LANG=en_US.UTF-8 - WORKSPACE="JSQMessages.xcworkspace" - IOS_SCHEME="JSQMessages" - IOS_SDK=iphonesimulator10.0 matrix: - DESTINATION="OS=8.1,name=iPhone 4s" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="NO" BUILD_EXAMPLE="YES" POD_LINT="YES" RUN_UI_TESTS="NO" - DESTINATION="OS=8.2,name=iPhone 5" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO" RUN_UI_TESTS="NO" - DESTINATION="OS=8.3,name=iPhone 5s" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO" RUN_UI_TESTS="NO" - DESTINATION="OS=8.4,name=iPhone 6" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO" RUN_UI_TESTS="NO" - DESTINATION="OS=9.0,name=iPhone 6 Plus" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" RUN_UI_TESTS="NO" - DESTINATION="OS=9.1,name=iPhone 6s" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" RUN_UI_TESTS="NO" - DESTINATION="OS=9.2,name=iPhone 6s Plus" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" RUN_UI_TESTS="NO" - DESTINATION="OS=9.3,name=iPad Air 2" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" RUN_UI_TESTS="NO" - DESTINATION="OS=10.0,name=iPhone 6s" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" RUN_UI_TESTS="NO" before_install: - gem install cocoapods --pre script: - set -o pipefail - if [ $POD_LINT == "YES" ]; then pod lib lint; fi # TODO: enable after project re-organization # - if [ $BUILD_EXAMPLE == "YES" ]; then # xcodebuild clean build -project Example/Example.xcodeproj -scheme Example -sdk "$SDK" -destination "$DESTINATION" ONLY_ACTIVE_ARCH=NO | xcpretty -c; # fi - if [ $RUN_TESTS == "YES" ]; then xcodebuild clean build test -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO | xcpretty -c; else xcodebuild clean build -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO | xcpretty -c; fi # TODO: enable after project re-organization # - if [ $RUN_UI_TESTS == "YES" ]; then # xcodebuild test -project Example/Example.xcodeproj -scheme Example -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO | xcpretty -c; # fi # Build for reporting test coverage #- if [ $RUN_TESTS == "YES" ]; then # xcodebuild test -workspace JSQMessages.xcworkspace -scheme JSQMessages -sdk iphonesimulator GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES; # fi after_success: - bash <(curl -s https://codecov.io/bash); ================================================ FILE: CHANGELOG.md ================================================ # CHANGELOG The changelog for `JSQMessagesViewController`. Also see the [releases](https://github.com/jessesquires/JSQMessagesViewController/releases) on GitHub. -------------------------------------- 8.0.0 ----- This release closes the [8.0.0 milestone](https://github.com/jessesquires/JSQMessagesViewController/milestones/8.0.0). ### Breaking changes - Removed `JSQSystemSoundPlayer` as a dependency, see #1649 for reasoning. You can easily still include this in your project by adding `pod 'JSQSystemSoundPlayer'` to your Podfile. You can find out the latest on `JSQSystemSoundPlayer` [here](https://github.com/jessesquires/JSQSystemSoundPlayer). - Removed `JSQMessagesKeyboardController` and implemented a proper `inputAccessoryView`. (#1063, #1529) Thanks @LeoNatan and @kirualex! - `JSQMessagesToolbarButtonFactory` is now an instance, not just class methods. (#1651, #866) Thanks @burntheroad! - `JSQMessagesAvatarImageFactory` is now an instance, not just class methods. (#1659, #1657) Thanks @burntheroad! - `JSQMessagesInputToolbar` now provides more control over the placement of the send button. (#840) Thanks @davidchiles! ### Enhancements - Better Swift inter-op. Implemented Objective-C nullability. (#1654) Thanks @Lucashuang0802! - Animated typing indicator. Typing indicator now animates like iMessage. (#1382) Thanks @radekcieciwa! - Dynamic text support. (#497, #1747) Thanks @MacMeDan! - Message cells now have a customizable accessory view. (#1519, #1719) Thanks @adubr! - Send button now can be turned on/off manually. (#1575, #1609) Thanks @sebastianludwig! - Video message items now have a custom thumbnail option. (#628, #709, #1408, #1823) Thanks @weekwood, @benjaminhallock! - A new class `JSQMessagesVideoThumbnailFactory` now can generate thumbnail images from `AVURLAsset`. (#709, #1823) Thanks @weekwood, @Lucashuang0802! - Added a `placeHolderInsets` property to `JSQMessagesComposerTextView` to allow insetting the placeholder text. (#1908) ### Fixes - Fixed a number of issues regarding keyboard handling. Keyboard handling is now much more stable. (#1063, #1529, #799, #941, #1299, #558, #557) - Fixed potential crash with media cells. (#1377, #1741) Thanks @Lucashuang0802! 7.3.4 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.3.4+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.3.4) 7.3.3 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.3.3+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.3.3) 7.3.2 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.3.2+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.3.2) 7.3.1 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.3.1+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.3.1) 7.3.0 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.3.0+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.3.0) 7.2.0 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.2.0+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.2.0) 7.1.0 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.1.0+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.1.0) 7.0.2 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.0.2+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.0.2) 7.0.1 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.0.1+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.0.1) 7.0.0 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A7.0.0+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.0.0) 6.1.3 ----- - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/6.1.3) 6.1.2 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A6.1.2+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/6.1.2) 6.1.1 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A6.1.1+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/6.1.1) 6.0.0 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A6.0.0+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/6.0.0) 5.3.0 ----- - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/5.3.0) 5.2.0 ----- - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/5.2.0) 5.1.0 ----- - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/5.1.0) 5.0.3 ----- - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/5.0.3) 5.0.0 ----- - [Milestone](https://github.com/jessesquires/JSQMessagesViewController/issues?q=milestone%3A5.0.0+is%3Aclosed) - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/5.0.0) 4.0.0 ----- - [GitHub release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/4.0.0) All previous versions --------------------- Unfortunately, release notes are not available for earlier versions of the library. ================================================ FILE: Documentation/apps_using_this_library.md ================================================ # Apps using this library These are the (known) apps that use `JSQMessagesViewController`. Submit a [pull request](https://github.com/jessesquires/JSQMessagesViewController/compare) to join the list! :smile: ----------------- * [Hemoglobe](http://bit.ly/hemoglobeapp) * [PocketSuite](https://itunes.apple.com/us/app/pocketsuite/id721795146) * [FireChat](https://itunes.apple.com/us/app/firechat/id719829352) * [Signal](https://github.com/WhisperSystems/Signal-iOS) * [ClassDojo](https://itunes.apple.com/us/app/classdojo/id552602056) * [Schools App](https://itunes.apple.com/us/app/schools-app/id495845755) * [ChatSecure](https://chatsecure.org) * [Bryx 911](https://itunes.apple.com/us/app/bryx-911/id813078029) * [Kytt](https://itunes.apple.com/de/app/kytt-neue-leute-in-der-umgebung/id848959696) * [Spark Social](https://itunes.apple.com/us/app/spark-social/id823785892) * [Spabbit](https://itunes.apple.com/us/app/spabbit/id737363908) * [Elodie](https://itunes.apple.com/app/elodie/id821610181) * [Instaply](https://itunes.apple.com/us/app/instaply/id558562920) * [Loopse](https://itunes.apple.com/us/app/loopse-spots-friends-sessions/id704783915) * [Oxwall Messenger](https://github.com/tochman/OxwallMessenger) * [FourChat](https://itunes.apple.com/us/app/fourchat/id650833730) * [vCinity](https://itunes.apple.com/us/app/vcinity-chat-without-internet/id875395391) * [Quick Text Message](https://itunes.apple.com/us/app/quick-text-message-fast-sms/id583729997) * [Libraries for developers](https://itunes.apple.com/us/app/libraries-for-developers/id653427112) * [Buhz|Hyve](https://itunes.apple.com/us/app/buhz-hyve/id818568956) * [Ringring.io](https://github.com/ringring-io/ringring-ios) * [gDecide](https://itunes.apple.com/ca/app/gdecide/id716801285) * [AwesomeChat](https://github.com/relatedcode/RealtimeChat) * [ParseChat](https://github.com/relatedcode/ParseChat) * [Jib](http://jibapp.com) * [Onvolo](https://itunes.apple.com/us/app/onvolo/id869332351) * [EVCloudKitDao](https://github.com/evermeer/EVCloudKitDao) * [Fluky Chat](https://itunes.apple.com/us/app/fluky-chat-secure-anonymous/id958605886) * [VillageUnity](https://itunes.apple.com/us/app/village-unity/id919972368) * [Pine](https://itunes.apple.com/us/app/pine-innovation-product-life/id946589228) * [NotificationChat](https://github.com/relatedcode/EncryptedChat) * [RealtimeChat](https://github.com/relatedcode/RealtimeChat) * [Bazar](https://itunes.apple.com/ru/app/bazar-talk-about-everything/id885453058) * [Roomie](https://itunes.apple.com/us/app/roomie-find-your-roomie/id962585201) * [PimpMyCall](https://itunes.apple.com/us/app/pimp-my-call/id990167537) * [Yellow Partner](https://itunes.apple.com/us/app/yellow-partner/id1062994361?ls=1&mt=8) * [Radiate](https://itunes.apple.com/us/app/radiate/id939284774?mt=8) * [Criptext](https://itunes.apple.com/us/app/criptext-secure-messenger/id848647361?mt=8) * [Chaty](https://github.com/LunarFlash/Chaty) * [SnipSnap](http://go.snipsnap.it/messageui-scout) * [BubbleMe](https://itunes.apple.com/us/app/bubbleme/id1125325038) * [Social-Go](https://github.com/kingreza/Social-Go) * [StudyBuddy](https://itunes.apple.com/ca/app/studybuddy/id948997336?mt=8) * [multipeer-chat](https://github.com/J4awesome/multipeer-chat) * [FriendlyU](https://itunes.apple.com/us/app/friendlyu/id963421205) * [LŌC](https://itunes.apple.com/us/app/loc-location-based-social/id957193908?mt=8) * [Ginger.io](https://itunes.apple.com/us/app/ginger.io-coaching-therapy/id515118602) * *Your app here, submit a [pull request](https://github.com/jessesquires/JSQMessagesViewController/compare)!* ================================================ FILE: Documentation/contributor_onboarding.md ================================================ # Contributor Onboarding *Contributor onboarding guide for JSQMessagesViewController* This guide is intended to bring new core contributors up-to-speed on the project, organization, expectations, and best practices. ------------------------- ## Introduction Welcome! :smile: If you are reading this, then you are (or are about to be) a core contributor! :tada: The goal of this document is to cover everything you need to know about helping to maintain this project. If you are not familiar with the code, the docs, the demo project, and everything else in the repo, then that should be your first step. Otherwise, continue on! ## Getting push access Being a **contributor** means submitting pull requests, opening issues, etc. Being a **core contributor** means getting push access and other permissions. We love freely giving push access to great contributors, and always err on trusting contributors with this responsibility. However, before granting you push access we would like to see a few things: - An interest and dedication to the project - Helping to triage issues, review pull requests, and diagnose bugs - Submitting a couple great pull requests We really prefer to grant push access to contributors who have a decent amount of time to share each week or month. If you cannot be extremely active on the project — that's ok! You can still be an :sparkles: awesome contributor :sparkles: without getting push access! The rationale behind all of this is that we do not want to accumulate a *huge* list of **core contributors** that are *not* regularly active. Remember, *your time* is *your time* — there is absolutely no pressure on you to spend a lot of time on this project, although it is greatly appreciated! :smile: > **Note:** the rest of this document applies to both **contributors** and **core contributors**, but there are some details that would require having push access. ## Core team ### Project lead Jesse Squires ([**@jessesquires**](https://github.com/jessesquires)) serves as the lead for `JSQMessagesViewController`. Responsibilities include: - Managing releases and CocoaPods distributions - Merging code into `master` - Overall guidance on design, architecture, and implementation - Strategic direction for the library - Onboarding new core contributors - Everything under **Core Contributors** :smile: - Anything not covered by **Core Contributors** :smile: As core contributors grow and take on more repsonsibility, they can become a lead. ### Core contributors Core contributors have push access and are responsible for: - Bug fixes - New features - Triaging issues (managing, organizing) - Reviewing pull requests - Answering questions from the community on [issues](https://github.com/jessesquires/JSQMessagesViewController/issues?utf8=✓&q=is%3Aissue+label%3A%22questions+%26+help%22+) and [StackOverflow](http://stackoverflow.com/questions/tagged/jsqmessagesviewcontroller) - Documentation Current core contributors: - Harlan Haskans ([**@harlanhaskins**](https://github.com/harlanhaskins)) - Eli Burke ([**@eliburke**](https://github.com/eliburke)) - Sebastian Ludwig ([**@sebastianludwig**](https://github.com/sebastianludwig)) ## Pushing code Although you have permissions to push code directly to `develop` as a core contributor, we ask that you *always* submit a pull request for code changes. After a code review and approval, you may merge your diff. For minor changes, like formatting or typos, pushing directly to `develop` is acceptable. Always merge work to `develop` unless otherwise specified. The project lead will manage the `master` branch. For now, Jesse ([**@jessesquires**](https://github.com/jessesquires)) should provide the final approval for *all* pull requests. However, as core contributors grow and establish themselves in the project, they can take on this responsibility as well. ## Project managment ### General guidelines Above all, abide by our [code of conduct](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/CONDUCT.md) at all times. Be welcoming, kind, and inclusive. Often, users do not follow our [contributing guidelines](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/CONTRIBUTING.md), fail to complete the [issue template](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/ISSUE_TEMPLATE.md), or fail to complete the [pull request template](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/PULL_REQUEST_TEMPLATE.md). This is frustrating, but the best response is to kindly remind and encourage them to follow the correct procedures next time. When first responding to a newly opened issue or pull request, *always* thank the contributor and add some sweet emoji. Any positive emoji will work. (:+1:, :smile:, :sunglasses:, etc.) Choose your favorite. > Thanks **@jessesquires**! :smile_cat: Then continue on with the rest of your comment. There will be times where we simply cannot accept a patch for various reasons. In this case, kindly explain why it is not the right approach for the library, thank them for their time and effort, and encourage them to keep contributing. In any situation, when in doubt, tag the project lead in a comment to get feedback. ### Development - All work for minor and patch releases should happen on `develop`. For example, release 7.x.x. - All work for major releases should happen on a release branch. For example, `release_8.0`. - The project lead will manage the `master` branch. For core contributors, always assign issues or pull requests to the appropriate team member. If you are working on an issue, assign it to yourself. If you would like someone to review a pull request, assign it to them. ### Managing issues - Always add the appropriate label(s). There may be more than one. - Assign to a release milestone, if applicable. - Ask for more information from the user, if needed. - Verify bugs. Leave comments on your findings as necessary. - If it's a duplicate, label and close. - Follow the general guidelines above. ##### Special labels - [`needs review`](https://github.com/jessesquires/JSQMessagesViewController/issues?q=is%3Aissue+label%3A%22needs+review%22): These issues need to be triaged and confirmed. They are typically bugs or pull requests, but do not have to be. Once verified, `needs review` should be removed and any other appropriate labels should be added. - [`new release roadmap`](https://github.com/jessesquires/JSQMessagesViewController/issues?utf8=✓&q=is%3Aissue+label%3A%22new+release+roadmap%22+): For communicating new releases to the community. - [`in-progress`](https://github.com/jessesquires/JSQMessagesViewController/issues?q=is%3Aissue+label%3Ain-progress): Specifies a task that is currently being worked on. Remove this label after closing a task. - [`duplicate`](https://github.com/jessesquires/JSQMessagesViewController/issues?utf8=✓&q=label%3Aduplicate+): For duplicate isses. When closing an issue as a duplicate be sure to leave a comment with the original issue number. *"Closing as duplicate of #6."* - [`questions & help`](https://github.com/jessesquires/JSQMessagesViewController/issues?q=is%3Aissue+label%3A%22questions+%26+help%22): For community questions and help. Note that we are trying to refer questions to [StackOverflow](http://stackoverflow.com/questions/tagged/jsqmessagesviewcontroller) instead. ### Managing pull requests - Review the code for correctness, performance, style, etc. Leave comments as needed. - Always add the appropriate label(s). There may be more than one. - Assign to a release milestone, if applicable. - Follow the general guidelines above. - If you think it's ready to go, tag the project lead to get the final :+1: ### Managing releases All releases are organized using [milestones](https://github.com/jessesquires/JSQMessagesViewController/milestones). Use these to prioritize work and figure out what's next. Issues and pull requests included in the next milestone release should be the highest priorty. Once a milestone is 100% complete, the project lead will merge `develop` or other release branches into `master`. The project lead will close the milestone, tag the release, and submit to CocoaPods. ### Managing documentation Having high quality documentation and 100% coverage has a significant impact on the project's success. Always add new docs for new public APIs and keep them up-to-date. Use existing docs and Apple's docs for Cocoa as guidelines for writing great documentation. ================================================ FILE: Documentation/faq.md ================================================ # FAQ *Frequently asked questions for JSQMessagesViewController.* Contributions are welcome! Please submit a [pull request](https://github.com/jessesquires/JSQMessagesViewController/compare). ------------------------------------ ## For 7.x.x #### Using `UITabBar` ? Is the library compatible with `UITabBarController` and `UITabBar`? Yes and no. For the history on this issue, see [#179](https://github.com/jessesquires/JSQMessagesViewController/issues/179) and [#94](https://github.com/jessesquires/JSQMessagesViewController/issues/94). This seems to be the best workaround: ````objective-c - (void)viewDidLoad { [super viewDidLoad]; self.edgesForExtendedLayout = UIRectEdgeNone; } ```` #### *Springy bubbles?* :warning: Note: this feature is still experimental. ````objective-c - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; self.collectionView.collectionViewLayout.springinessEnabled = YES; } ```` #### *Remove avatars?* ````objective-c - (void)viewDidLoad { [super viewDidLoad]; self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero; self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero; } - (id)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath { return nil; } ```` #### *Need customize your collection view cells?* There are 2 approaches to this, which one you choose depends on your needs. 1. Customize appearance and behavior of existing cells. (Easy) 2. Provide your own completely custom cell prototypes. (Hard) > Also see [previous issues](https://github.com/jessesquires/JSQMessagesViewController/issues?utf8=✓&q=custom+cell+in%3Atitle). ##### (1) Customizing existing cells If you only need to make minor changes to the existing cells (colors, data detectors, etc.), then you simply need to override the following method. You have access to all properties on the cell. ([docs](http://cocoadocs.org/docsets/JSQMessagesViewController/7.2.0/Classes/JSQMessagesCollectionViewCell.html)) ````objective-c - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath]; // Customize the shit out of this cell // See the docs for JSQMessagesCollectionViewCell return cell; } ```` ##### (2) Providing your own cell prototypes This approach is more involved, but gives you greater flexibility. If you need to add or modify subviews of the cell, use this approach. ([docs](http://cocoadocs.org/docsets/JSQMessagesViewController/7.2.0/Classes/JSQMessagesViewController.html)) 1. You need to provide your own cell subclasses, similar to the library's `JSQMessagesCollectionViewCell`, `JSQMessagesCollectionViewCellIncoming`, `JSQMessagesCollectionViewCellOutgoing`. 2. On your `JSQMessagesViewController` subclass, set the following properties according to your classes: - `outgoingCellIdentifier` - `outgoingMediaCellIdentifier` - `incomingCellIdentifier` - `incomingMediaCellIdentifier` 3. Register your cell classes/nibs with the collection view and the identifiers above 4. Override `-collectionView: cellForItemAtIndexPath:`. Do not call `super`. Since you are providing your own cells, calling `super` will perform a bunch of unnecessary work. 5. (Optional) For your model objects, implement `JSQMessageData` or subclass `JSQMessage` and extend to your needs. For more detailed instructions on creating custom cells, see [this comment](https://github.com/jessesquires/JSQMessagesViewController/issues/1739#issuecomment-246489889) #### *Customize your toolbar buttons?* ````objective-c - (void)viewDidLoad { [super viewDidLoad]; // This button will call the `didPressAccessoryButton:` selector on your JSQMessagesViewController subclass self.inputToolbar.contentView.leftBarButtonItem = /* custom button or nil to remove */ // This button will call the `didPressSendButton:` selector on your JSQMessagesViewController subclass self.inputToolbar.contentView.rightBarButtonItem = /* custom button or nil to remove */ // Swap buttons, move send button to the LEFT side and the attachment button to the RIGHT // For RTL language support self.inputToolbar.contentView.leftBarButtonItem = [JSQMessagesToolbarButtonFactory defaultSendButtonItem]; self.inputToolbar.contentView.rightBarButtonItem = [JSQMessagesToolbarButtonFactory defaultAccessoryButtonItem]; // The library will call the correct selector for each button, based on this value self.inputToolbar.sendButtonOnRight = NO; } ```` ================================================ FILE: Documentation/getting_started.md ================================================ # Getting Started *Getting started guide for JSQMessagesViewController* ----------------------------- ## For versions 6.x and 7.x ````objective-c #import // import all the things ```` * **Tutorials and blogs** * Read the [blog post](http://www.jessesquires.com/introducing-jsqmessagesvc-6-0/) about the 6.0 release! * Ray Wenderlich has a [great tutorial](http://www.raywenderlich.com/122148/firebase-tutorial-real-time-chat), written by [David East](https://twitter.com/_davideast). (For 7.x releases) * **Demo Project** * There's a sweet demo project: `JSQMessages.xcworkspace`. * Run `pod install` first. * Swift Example can be found in the SwiftExample folder just open the `SwiftExample.xcworkspace`. * Run `pod install` first. * [Firebase](https://www.firebase.com) also has a sweet [demo project](https://github.com/firebase/ios-swift-chat-example), and it's in Swift! * **Message Model** * Your message model objects should conform to the `JSQMessageData` protocol. * However, you may use the provided `JSQMessage` class. * **Media Attachment Model** * Your media attachment model objects should conform to the `JSQMessageMediaData` protocol. * However, you may use the provided classes: `JSQPhotoMediaItem`, `JSQLocationMediaItem`, `JSQVideoMediaItem`. * Creating your own custom media items is easy! Simply follow the pattern used by the built-in media types. * Also see `JSQMessagesMediaViewBubbleImageMasker` for masking your custom media views as message bubbles. * **Avatar Model** * Your avatar model objects should conform to the `JSQMessageAvatarImageDataSource` protocol. * However, you may use the provided `JSQMessagesAvatarImage` class. * Also see `JSQMessagesAvatarImageFactory` for easily generating custom avatars. * **Message Bubble Model** * Your message bubble model objects should conform to the `JSQMessageBubbleImageDataSource` protocol. * However, you may use the provided `JSQMessagesBubbleImage` class. * Also see `JSQMessagesBubbleImageFactory` and `UIImage+JSQMessages.h` for easily generating custom bubbles. * **View Controller** * Subclass `JSQMessagesViewController`. * Implement the required methods in the `JSQMessagesCollectionViewDataSource` protocol. * Implement the required methods in the `JSQMessagesCollectionViewDelegateFlowLayout` protocol. * Set your `senderId` and `senderDisplayName`. These properties correspond to the methods found in `JSQMessageData` and determine which messages are incoming or outgoing. * **Customizing** * The demo project is well-commented. Please use this as a guide. ## Previous versions Sorry! Guides are not available for older versions of the library. ================================================ FILE: Documentation/migration.md ================================================ # Migration Guide *Migrating between major versions of JSQMessagesViewController?* ----------------------------- ## From `6.x` to `7.x` See the [7.0 release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/7.0.0) for details about API changes. ## From `5.x` to `6.x` See the [6.0 release notes](https://github.com/jessesquires/JSQMessagesViewController/releases/tag/6.0.0) for details about API changes. ## Previous versions Unfortunately, versions prior to `5.0` outdate this document, and guides are not available. ================================================ FILE: JSQMessages.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1F0EFE0F1AC23D7E003FF3DB /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F0EFE0E1AC23D7E003FF3DB /* MobileCoreServices.framework */; }; 50B7F5A81CA401FA009A44F5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 50B7F5AA1CA401FA009A44F5 /* Localizable.strings */; }; 54271E3B1C90469100294290 /* jsq_messages_sample.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 54271E3A1C90469100294290 /* jsq_messages_sample.m4a */; }; 54271E3E1C905B9200294290 /* JSQAudioMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 54271E3D1C905B9200294290 /* JSQAudioMediaItem.m */; }; 54271E401C905D1600294290 /* JSQAudioMediaItemTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54271E3F1C905D1600294290 /* JSQAudioMediaItemTests.m */; }; 544A32211CB2EE380084BFC0 /* JSQAudioMediaViewAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 544A32201CB2EE380084BFC0 /* JSQAudioMediaViewAttributes.m */; }; 88078A9D19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88078A9C19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.m */; }; 88324C3419F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88324C3319F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m */; }; 883C11781A09FB100092A16D /* JSQMessagesCellTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 883C11771A09FB100092A16D /* JSQMessagesCellTextView.m */; }; 88445B3119E0AE3F0014F889 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3019E0AE3F0014F889 /* UIKit.framework */; }; 88445B3319E0AE450014F889 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3219E0AE450014F889 /* Foundation.framework */; }; 88445B3519E0AE4A0014F889 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3419E0AE4A0014F889 /* CoreGraphics.framework */; }; 88445B3719E0AE5C0014F889 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3619E0AE5C0014F889 /* QuartzCore.framework */; }; 88445B3819E0C0A70014F889 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3019E0AE3F0014F889 /* UIKit.framework */; }; 88445B3919E0C0AC0014F889 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3219E0AE450014F889 /* Foundation.framework */; }; 88445B3B19E0C0B10014F889 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3A19E0C0B10014F889 /* XCTest.framework */; }; 88445B3C19E0C0B80014F889 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3419E0AE4A0014F889 /* CoreGraphics.framework */; }; 88445B3D19E0C0BE0014F889 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B3619E0AE5C0014F889 /* QuartzCore.framework */; }; 88445B4019E1B4470014F889 /* JSQLocationMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 88445B3F19E1B4470014F889 /* JSQLocationMediaItem.m */; }; 88445B4219E1B50B0014F889 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B4119E1B50B0014F889 /* CoreLocation.framework */; }; 88445B4419E1B5110014F889 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B4319E1B5110014F889 /* MapKit.framework */; }; 88445B4519E1B5210014F889 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B4119E1B50B0014F889 /* CoreLocation.framework */; }; 88445B4619E1B5290014F889 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88445B4319E1B5110014F889 /* MapKit.framework */; }; 8861666D19F492B70025B958 /* JSQMessagesAssets.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 8861666C19F492B70025B958 /* JSQMessagesAssets.bundle */; }; 886C33FD19F4371E006B4997 /* JSQVideoMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 886C33FC19F4371E006B4997 /* JSQVideoMediaItem.m */; }; 886C33FF19F45E30006B4997 /* JSQMessagesViewController.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 886C33FE19F45E30006B4997 /* JSQMessagesViewController.podspec */; }; 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 */; }; 88A25F3919D8DF2500924534 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88A25F3019D8DF2500924534 /* Main.storyboard */; }; 88A25F3A19D8DF2500924534 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88A25F3219D8DF2500924534 /* Images.xcassets */; }; 88A25F3C19D8DF2500924534 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F3419D8DF2500924534 /* main.m */; }; 88A25FB619D8E01A00924534 /* NSString+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F5819D8E01A00924534 /* NSString+JSQMessages.m */; }; 88A25FB719D8E01A00924534 /* UIColor+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F5A19D8E01A00924534 /* UIColor+JSQMessages.m */; }; 88A25FB819D8E01A00924534 /* UIImage+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F5C19D8E01A00924534 /* UIImage+JSQMessages.m */; }; 88A25FB919D8E01A00924534 /* UIView+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F5E19D8E01A00924534 /* UIView+JSQMessages.m */; }; 88A25FBB19D8E01A00924534 /* JSQMessagesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F6319D8E01A00924534 /* JSQMessagesViewController.m */; }; 88A25FBC19D8E01A00924534 /* JSQMessagesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 88A25F6419D8E01A00924534 /* JSQMessagesViewController.xib */; }; 88A25FBD19D8E01A00924534 /* JSQMessagesAvatarImageFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F6719D8E01A00924534 /* JSQMessagesAvatarImageFactory.m */; }; 88A25FBE19D8E01A00924534 /* JSQMessagesBubbleImageFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F6919D8E01A00924534 /* JSQMessagesBubbleImageFactory.m */; }; 88A25FBF19D8E01A00924534 /* JSQMessagesTimestampFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F6B19D8E01A00924534 /* JSQMessagesTimestampFormatter.m */; }; 88A25FC019D8E01A00924534 /* JSQMessagesToolbarButtonFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F6D19D8E01A00924534 /* JSQMessagesToolbarButtonFactory.m */; }; 88A25FC119D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F7119D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.m */; }; 88A25FC219D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F7319D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m */; }; 88A25FC319D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F7519D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.m */; }; 88A25FC519D8E01A00924534 /* JSQMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F7A19D8E01A00924534 /* JSQMessage.m */; }; 88A25FC619D8E01A00924534 /* JSQMessagesAvatarImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F8019D8E01A00924534 /* JSQMessagesAvatarImage.m */; }; 88A25FC719D8E01A00924534 /* JSQMessagesBubbleImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F8219D8E01A00924534 /* JSQMessagesBubbleImage.m */; }; 88A25FC819D8E01A00924534 /* JSQPhotoMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F8619D8E01A00924534 /* JSQPhotoMediaItem.m */; }; 88A25FCA19D8E01A00924534 /* JSQMessagesCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F8B19D8E01A00924534 /* JSQMessagesCollectionView.m */; }; 88A25FCB19D8E01A00924534 /* JSQMessagesCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F8D19D8E01A00924534 /* JSQMessagesCollectionViewCell.m */; }; 88A25FCC19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F8F19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.m */; }; 88A25FCD19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.xib in Resources */ = {isa = PBXBuildFile; fileRef = 88A25F9019D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.xib */; }; 88A25FCE19D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F9219D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.m */; }; 88A25FCF19D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.xib in Resources */ = {isa = PBXBuildFile; fileRef = 88A25F9319D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.xib */; }; 88A25FD019D8E01A00924534 /* JSQMessagesComposerTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F9519D8E01A00924534 /* JSQMessagesComposerTextView.m */; }; 88A25FD119D8E01A00924534 /* JSQMessagesInputToolbar.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F9719D8E01A00924534 /* JSQMessagesInputToolbar.m */; }; 88A25FD219D8E01A00924534 /* JSQMessagesLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F9919D8E01A00924534 /* JSQMessagesLabel.m */; }; 88A25FD319D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F9B19D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.m */; }; 88A25FD419D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 88A25F9C19D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.xib */; }; 88A25FD519D8E01A00924534 /* JSQMessagesToolbarContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F9E19D8E01A00924534 /* JSQMessagesToolbarContentView.m */; }; 88A25FD619D8E01A00924534 /* JSQMessagesToolbarContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 88A25F9F19D8E01A00924534 /* JSQMessagesToolbarContentView.xib */; }; 88A25FD719D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FA119D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.m */; }; 88A25FD819D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 88A25FA219D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.xib */; }; 88A25FDF19D8E0C400924534 /* DemoMessagesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FDA19D8E0C400924534 /* DemoMessagesViewController.m */; }; 88A25FE019D8E0C400924534 /* DemoModelData.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FDC19D8E0C400924534 /* DemoModelData.m */; }; 88A25FE119D8E0C400924534 /* TableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FDE19D8E0C400924534 /* TableViewController.m */; }; 88A2600119D8E18400924534 /* JSQMessagesNSStringTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FE319D8E18400924534 /* JSQMessagesNSStringTests.m */; }; 88A2600219D8E18400924534 /* JSQMessagesUIColorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FE419D8E18400924534 /* JSQMessagesUIColorTests.m */; }; 88A2600319D8E18400924534 /* JSQMessagesUIImageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FE519D8E18400924534 /* JSQMessagesUIImageTests.m */; }; 88A2600419D8E18400924534 /* JSQMessagesUIViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FE619D8E18400924534 /* JSQMessagesUIViewTests.m */; }; 88A2600619D8E18400924534 /* JSQMessagesViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FE919D8E18400924534 /* JSQMessagesViewControllerTests.m */; }; 88A2600719D8E18400924534 /* JSQMessagesAvatarImageFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FEB19D8E18400924534 /* JSQMessagesAvatarImageFactoryTests.m */; }; 88A2600819D8E18400924534 /* JSQMessagesBubbleImageFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FEC19D8E18400924534 /* JSQMessagesBubbleImageFactoryTests.m */; }; 88A2600919D8E18400924534 /* JSQMessagesTimestampFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FED19D8E18400924534 /* JSQMessagesTimestampFormatterTests.m */; }; 88A2600A19D8E18400924534 /* JSQMessagesToolbarButtonFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FEE19D8E18400924534 /* JSQMessagesToolbarButtonFactoryTests.m */; }; 88A2600B19D8E18400924534 /* JSQMessagesCollectionViewFlowLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FF019D8E18400924534 /* JSQMessagesCollectionViewFlowLayoutTests.m */; }; 88A2600C19D8E18400924534 /* JSQMessagesCollectionViewLayoutAttributesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FF119D8E18400924534 /* JSQMessagesCollectionViewLayoutAttributesTests.m */; }; 88A2600D19D8E18400924534 /* JSQMessageMediaTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FF319D8E18400924534 /* JSQMessageMediaTests.m */; }; 88A2600E19D8E18400924534 /* JSQMessagesAvatarImageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FF419D8E18400924534 /* JSQMessagesAvatarImageTests.m */; }; 88A2600F19D8E18400924534 /* JSQMessagesBubbleImageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FF519D8E18400924534 /* JSQMessagesBubbleImageTests.m */; }; 88A2601019D8E18400924534 /* JSQMessageTextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FF619D8E18400924534 /* JSQMessageTextTests.m */; }; 88A2601219D8E18400924534 /* JSQMessagesCollectionViewCellTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FF919D8E18400924534 /* JSQMessagesCollectionViewCellTests.m */; }; 88A2601319D8E18400924534 /* JSQMessagesCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FFA19D8E18400924534 /* JSQMessagesCollectionViewTests.m */; }; 88A2601419D8E18400924534 /* JSQMessagesComposerTextViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FFB19D8E18400924534 /* JSQMessagesComposerTextViewTests.m */; }; 88A2601519D8E18400924534 /* JSQMessagesInputToolbarTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FFC19D8E18400924534 /* JSQMessagesInputToolbarTests.m */; }; 88A2601619D8E18400924534 /* JSQMessagesLabelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FFD19D8E18400924534 /* JSQMessagesLabelTests.m */; }; 88A2601719D8E18400924534 /* JSQMessagesLoadEarlierHeaderViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FFE19D8E18400924534 /* JSQMessagesLoadEarlierHeaderViewTests.m */; }; 88A2601819D8E18400924534 /* JSQMessagesToolbarContentViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FFF19D8E18400924534 /* JSQMessagesToolbarContentViewTests.m */; }; 88A2601919D8E18400924534 /* JSQMessagesTypingIndicatorFooterViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A2600019D8E18400924534 /* JSQMessagesTypingIndicatorFooterViewTests.m */; }; 88A2601B19D8E45600924534 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 88A2601A19D8E45600924534 /* Info.plist */; }; 88A901B619F618B100F99777 /* JSQMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A901B519F618B100F99777 /* JSQMediaItem.m */; }; 88B5C41F1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B5C41E1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m */; }; 88C00A4E1A44D4C600B004B3 /* JSQLocationMediaItemTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C00A4D1A44D4C600B004B3 /* JSQLocationMediaItemTests.m */; }; 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 */; }; A04B0EBF1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = A04B0EBE1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.m */; }; BF10D6AA1D062AD10072D215 /* JSQMessagesTypingView.m in Sources */ = {isa = PBXBuildFile; fileRef = BF10D6A91D062AD10072D215 /* JSQMessagesTypingView.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 88A25F1C19D8DEC500924534 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 88A25EFA19D8DEC400924534 /* Project object */; proxyType = 1; remoteGlobalIDString = 88A25F0119D8DEC400924534; remoteInfo = JSQMessages; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 1F0EFE0E1AC23D7E003FF3DB /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 50B7F5A51CA3FF4E009A44F5 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Main.strings; sourceTree = ""; }; 50B7F5A91CA401FA009A44F5 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; 50B7F5AB1CA40202009A44F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 54271E3A1C90469100294290 /* jsq_messages_sample.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = jsq_messages_sample.m4a; sourceTree = ""; }; 54271E3C1C905B9200294290 /* JSQAudioMediaItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQAudioMediaItem.h; sourceTree = ""; }; 54271E3D1C905B9200294290 /* JSQAudioMediaItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQAudioMediaItem.m; sourceTree = ""; }; 54271E3F1C905D1600294290 /* JSQAudioMediaItemTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQAudioMediaItemTests.m; sourceTree = ""; }; 544A321F1CB2EE380084BFC0 /* JSQAudioMediaViewAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQAudioMediaViewAttributes.h; sourceTree = ""; }; 544A32201CB2EE380084BFC0 /* JSQAudioMediaViewAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQAudioMediaViewAttributes.m; sourceTree = ""; }; 58620BCC6ABA99E3C6FD36F5 /* JSQMessagesViewAccessoryButtonDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesViewAccessoryButtonDelegate.h; sourceTree = ""; }; 88078A9B19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesMediaPlaceholderView.h; sourceTree = ""; }; 88078A9C19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaPlaceholderView.m; sourceTree = ""; }; 88324C3319F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaViewBubbleImageMaskerTests.m; sourceTree = ""; }; 883C11761A09FB100092A16D /* JSQMessagesCellTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCellTextView.h; sourceTree = ""; }; 883C11771A09FB100092A16D /* JSQMessagesCellTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCellTextView.m; sourceTree = ""; }; 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; }; 88445B3619E0AE5C0014F889 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 88445B3A19E0C0B10014F889 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = System/Library/Frameworks/XCTest.framework; sourceTree = SDKROOT; }; 88445B3E19E1B4470014F889 /* JSQLocationMediaItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQLocationMediaItem.h; sourceTree = ""; }; 88445B3F19E1B4470014F889 /* JSQLocationMediaItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQLocationMediaItem.m; sourceTree = ""; }; 88445B4119E1B50B0014F889 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 88445B4319E1B5110014F889 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 8861666C19F492B70025B958 /* JSQMessagesAssets.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = JSQMessagesAssets.bundle; sourceTree = ""; }; 886C33FB19F4371E006B4997 /* JSQVideoMediaItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQVideoMediaItem.h; sourceTree = ""; }; 886C33FC19F4371E006B4997 /* JSQVideoMediaItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQVideoMediaItem.m; sourceTree = ""; }; 886C33FE19F45E30006B4997 /* JSQMessagesViewController.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = JSQMessagesViewController.podspec; sourceTree = ""; }; 8873B60A1AB7B244006DF9AC /* NSBundle+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+JSQMessages.h"; sourceTree = ""; }; 8873B60B1AB7B244006DF9AC /* NSBundle+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+JSQMessages.m"; sourceTree = ""; }; 8873B60D1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesNSBundleTests.m; sourceTree = ""; }; 8885734819DE540400E89D20 /* DemoSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoSettingsViewController.h; sourceTree = ""; }; 8885734919DE540400E89D20 /* DemoSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoSettingsViewController.m; sourceTree = ""; }; 8885734B19DE55D000E89D20 /* NSUserDefaults+DemoSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUserDefaults+DemoSettings.h"; sourceTree = ""; }; 8885734C19DE55D000E89D20 /* NSUserDefaults+DemoSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUserDefaults+DemoSettings.m"; sourceTree = ""; }; 88A25F0219D8DEC400924534 /* JSQMessages.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JSQMessages.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88A25F1B19D8DEC500924534 /* JSQMessagesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JSQMessagesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88A25F2019D8DEC500924534 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88A25F2C19D8DF2500924534 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 88A25F2D19D8DF2500924534 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 88A25F3119D8DF2500924534 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 88A25F3219D8DF2500924534 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 88A25F3419D8DF2500924534 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 88A25F5719D8E01A00924534 /* NSString+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+JSQMessages.h"; sourceTree = ""; }; 88A25F5819D8E01A00924534 /* NSString+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+JSQMessages.m"; sourceTree = ""; }; 88A25F5919D8E01A00924534 /* UIColor+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+JSQMessages.h"; sourceTree = ""; }; 88A25F5A19D8E01A00924534 /* UIColor+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+JSQMessages.m"; sourceTree = ""; }; 88A25F5B19D8E01A00924534 /* UIImage+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+JSQMessages.h"; sourceTree = ""; }; 88A25F5C19D8E01A00924534 /* UIImage+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+JSQMessages.m"; sourceTree = ""; }; 88A25F5D19D8E01A00924534 /* UIView+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+JSQMessages.h"; sourceTree = ""; }; 88A25F5E19D8E01A00924534 /* UIView+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+JSQMessages.m"; sourceTree = ""; }; 88A25F6219D8E01A00924534 /* JSQMessagesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesViewController.h; sourceTree = ""; }; 88A25F6319D8E01A00924534 /* JSQMessagesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesViewController.m; sourceTree = ""; }; 88A25F6419D8E01A00924534 /* JSQMessagesViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQMessagesViewController.xib; sourceTree = ""; }; 88A25F6619D8E01A00924534 /* JSQMessagesAvatarImageFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesAvatarImageFactory.h; sourceTree = ""; }; 88A25F6719D8E01A00924534 /* JSQMessagesAvatarImageFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesAvatarImageFactory.m; sourceTree = ""; }; 88A25F6819D8E01A00924534 /* JSQMessagesBubbleImageFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubbleImageFactory.h; sourceTree = ""; }; 88A25F6919D8E01A00924534 /* JSQMessagesBubbleImageFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubbleImageFactory.m; sourceTree = ""; }; 88A25F6A19D8E01A00924534 /* JSQMessagesTimestampFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesTimestampFormatter.h; sourceTree = ""; }; 88A25F6B19D8E01A00924534 /* JSQMessagesTimestampFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesTimestampFormatter.m; sourceTree = ""; }; 88A25F6C19D8E01A00924534 /* JSQMessagesToolbarButtonFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesToolbarButtonFactory.h; sourceTree = ""; }; 88A25F6D19D8E01A00924534 /* JSQMessagesToolbarButtonFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesToolbarButtonFactory.m; sourceTree = ""; }; 88A25F6E19D8E01A00924534 /* JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessages.h; sourceTree = ""; }; 88A25F7019D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewFlowLayout.h; sourceTree = ""; }; 88A25F7119D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewFlowLayout.m; sourceTree = ""; }; 88A25F7219D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewFlowLayoutInvalidationContext.h; sourceTree = ""; }; 88A25F7319D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewFlowLayoutInvalidationContext.m; sourceTree = ""; }; 88A25F7419D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewLayoutAttributes.h; sourceTree = ""; }; 88A25F7519D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewLayoutAttributes.m; sourceTree = ""; }; 88A25F7919D8E01A00924534 /* JSQMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessage.h; sourceTree = ""; }; 88A25F7A19D8E01A00924534 /* JSQMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessage.m; sourceTree = ""; }; 88A25F7B19D8E01A00924534 /* JSQMessageAvatarImageDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessageAvatarImageDataSource.h; sourceTree = ""; }; 88A25F7C19D8E01A00924534 /* JSQMessageBubbleImageDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessageBubbleImageDataSource.h; sourceTree = ""; }; 88A25F7D19D8E01A00924534 /* JSQMessageData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessageData.h; sourceTree = ""; }; 88A25F7E19D8E01A00924534 /* JSQMessageMediaData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessageMediaData.h; sourceTree = ""; }; 88A25F7F19D8E01A00924534 /* JSQMessagesAvatarImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesAvatarImage.h; sourceTree = ""; }; 88A25F8019D8E01A00924534 /* JSQMessagesAvatarImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesAvatarImage.m; sourceTree = ""; }; 88A25F8119D8E01A00924534 /* JSQMessagesBubbleImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubbleImage.h; sourceTree = ""; }; 88A25F8219D8E01A00924534 /* JSQMessagesBubbleImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubbleImage.m; sourceTree = ""; }; 88A25F8319D8E01A00924534 /* JSQMessagesCollectionViewDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewDataSource.h; sourceTree = ""; }; 88A25F8419D8E01A00924534 /* JSQMessagesCollectionViewDelegateFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewDelegateFlowLayout.h; sourceTree = ""; }; 88A25F8519D8E01A00924534 /* JSQPhotoMediaItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQPhotoMediaItem.h; sourceTree = ""; }; 88A25F8619D8E01A00924534 /* JSQPhotoMediaItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQPhotoMediaItem.m; sourceTree = ""; }; 88A25F8A19D8E01A00924534 /* JSQMessagesCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionView.h; sourceTree = ""; }; 88A25F8B19D8E01A00924534 /* JSQMessagesCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionView.m; sourceTree = ""; }; 88A25F8C19D8E01A00924534 /* JSQMessagesCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewCell.h; sourceTree = ""; }; 88A25F8D19D8E01A00924534 /* JSQMessagesCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCell.m; sourceTree = ""; }; 88A25F8E19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewCellIncoming.h; sourceTree = ""; }; 88A25F8F19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCellIncoming.m; sourceTree = ""; }; 88A25F9019D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQMessagesCollectionViewCellIncoming.xib; sourceTree = ""; }; 88A25F9119D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewCellOutgoing.h; sourceTree = ""; }; 88A25F9219D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCellOutgoing.m; sourceTree = ""; }; 88A25F9319D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQMessagesCollectionViewCellOutgoing.xib; sourceTree = ""; }; 88A25F9419D8E01A00924534 /* JSQMessagesComposerTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesComposerTextView.h; sourceTree = ""; }; 88A25F9519D8E01A00924534 /* JSQMessagesComposerTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesComposerTextView.m; sourceTree = ""; }; 88A25F9619D8E01A00924534 /* JSQMessagesInputToolbar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesInputToolbar.h; sourceTree = ""; }; 88A25F9719D8E01A00924534 /* JSQMessagesInputToolbar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesInputToolbar.m; sourceTree = ""; }; 88A25F9819D8E01A00924534 /* JSQMessagesLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesLabel.h; sourceTree = ""; }; 88A25F9919D8E01A00924534 /* JSQMessagesLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesLabel.m; sourceTree = ""; }; 88A25F9A19D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesLoadEarlierHeaderView.h; sourceTree = ""; }; 88A25F9B19D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesLoadEarlierHeaderView.m; sourceTree = ""; }; 88A25F9C19D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQMessagesLoadEarlierHeaderView.xib; sourceTree = ""; }; 88A25F9D19D8E01A00924534 /* JSQMessagesToolbarContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesToolbarContentView.h; sourceTree = ""; }; 88A25F9E19D8E01A00924534 /* JSQMessagesToolbarContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesToolbarContentView.m; sourceTree = ""; }; 88A25F9F19D8E01A00924534 /* JSQMessagesToolbarContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQMessagesToolbarContentView.xib; sourceTree = ""; }; 88A25FA019D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesTypingIndicatorFooterView.h; sourceTree = ""; }; 88A25FA119D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesTypingIndicatorFooterView.m; sourceTree = ""; }; 88A25FA219D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQMessagesTypingIndicatorFooterView.xib; sourceTree = ""; }; 88A25FD919D8E0C400924534 /* DemoMessagesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoMessagesViewController.h; sourceTree = ""; }; 88A25FDA19D8E0C400924534 /* DemoMessagesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoMessagesViewController.m; sourceTree = ""; }; 88A25FDB19D8E0C400924534 /* DemoModelData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoModelData.h; sourceTree = ""; }; 88A25FDC19D8E0C400924534 /* DemoModelData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoModelData.m; sourceTree = ""; }; 88A25FDD19D8E0C400924534 /* TableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableViewController.h; sourceTree = ""; }; 88A25FDE19D8E0C400924534 /* TableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TableViewController.m; sourceTree = ""; }; 88A25FE319D8E18400924534 /* JSQMessagesNSStringTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesNSStringTests.m; sourceTree = ""; }; 88A25FE419D8E18400924534 /* JSQMessagesUIColorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesUIColorTests.m; sourceTree = ""; }; 88A25FE519D8E18400924534 /* JSQMessagesUIImageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesUIImageTests.m; sourceTree = ""; }; 88A25FE619D8E18400924534 /* JSQMessagesUIViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesUIViewTests.m; sourceTree = ""; }; 88A25FE919D8E18400924534 /* JSQMessagesViewControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesViewControllerTests.m; sourceTree = ""; }; 88A25FEB19D8E18400924534 /* JSQMessagesAvatarImageFactoryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesAvatarImageFactoryTests.m; sourceTree = ""; }; 88A25FEC19D8E18400924534 /* JSQMessagesBubbleImageFactoryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubbleImageFactoryTests.m; sourceTree = ""; }; 88A25FED19D8E18400924534 /* JSQMessagesTimestampFormatterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesTimestampFormatterTests.m; sourceTree = ""; }; 88A25FEE19D8E18400924534 /* JSQMessagesToolbarButtonFactoryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesToolbarButtonFactoryTests.m; sourceTree = ""; }; 88A25FF019D8E18400924534 /* JSQMessagesCollectionViewFlowLayoutTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewFlowLayoutTests.m; sourceTree = ""; }; 88A25FF119D8E18400924534 /* JSQMessagesCollectionViewLayoutAttributesTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewLayoutAttributesTests.m; sourceTree = ""; }; 88A25FF319D8E18400924534 /* JSQMessageMediaTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessageMediaTests.m; sourceTree = ""; }; 88A25FF419D8E18400924534 /* JSQMessagesAvatarImageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesAvatarImageTests.m; sourceTree = ""; }; 88A25FF519D8E18400924534 /* JSQMessagesBubbleImageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubbleImageTests.m; sourceTree = ""; }; 88A25FF619D8E18400924534 /* JSQMessageTextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessageTextTests.m; sourceTree = ""; }; 88A25FF919D8E18400924534 /* JSQMessagesCollectionViewCellTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCellTests.m; sourceTree = ""; }; 88A25FFA19D8E18400924534 /* JSQMessagesCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewTests.m; sourceTree = ""; }; 88A25FFB19D8E18400924534 /* JSQMessagesComposerTextViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesComposerTextViewTests.m; sourceTree = ""; }; 88A25FFC19D8E18400924534 /* JSQMessagesInputToolbarTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesInputToolbarTests.m; sourceTree = ""; }; 88A25FFD19D8E18400924534 /* JSQMessagesLabelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesLabelTests.m; sourceTree = ""; }; 88A25FFE19D8E18400924534 /* JSQMessagesLoadEarlierHeaderViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesLoadEarlierHeaderViewTests.m; sourceTree = ""; }; 88A25FFF19D8E18400924534 /* JSQMessagesToolbarContentViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesToolbarContentViewTests.m; sourceTree = ""; }; 88A2600019D8E18400924534 /* JSQMessagesTypingIndicatorFooterViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesTypingIndicatorFooterViewTests.m; sourceTree = ""; }; 88A2601A19D8E45600924534 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88A901B419F618B100F99777 /* JSQMediaItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMediaItem.h; sourceTree = ""; }; 88A901B519F618B100F99777 /* JSQMediaItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMediaItem.m; sourceTree = ""; }; 88B5C41D1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubblesSizeCalculator.h; sourceTree = ""; }; 88B5C41E1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubblesSizeCalculator.m; sourceTree = ""; }; 88B5C4201B7C424700EC79D4 /* JSQMessagesBubbleSizeCalculating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubbleSizeCalculating.h; sourceTree = ""; }; 88C00A4D1A44D4C600B004B3 /* JSQLocationMediaItemTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQLocationMediaItemTests.m; sourceTree = ""; }; 88C00A4F1A44D4D800B004B3 /* JSQPhotoMediaItemTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQPhotoMediaItemTests.m; sourceTree = ""; }; 88C00A511A44D4E500B004B3 /* JSQVideoMediaItemTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQVideoMediaItemTests.m; sourceTree = ""; }; 88C4582E19F5F7A0008FD427 /* JSQMessagesMediaViewBubbleImageMasker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesMediaViewBubbleImageMasker.h; sourceTree = ""; }; 88C4582F19F5F7A0008FD427 /* JSQMessagesMediaViewBubbleImageMasker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaViewBubbleImageMasker.m; sourceTree = ""; }; A04B0EBD1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesVideoThumbnailFactory.h; sourceTree = ""; }; A04B0EBE1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesVideoThumbnailFactory.m; sourceTree = ""; }; BF10D6A81D062AD10072D215 /* JSQMessagesTypingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesTypingView.h; sourceTree = ""; }; BF10D6A91D062AD10072D215 /* JSQMessagesTypingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesTypingView.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 88A25EFF19D8DEC400924534 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1F0EFE0F1AC23D7E003FF3DB /* MobileCoreServices.framework in Frameworks */, 88445B4419E1B5110014F889 /* MapKit.framework in Frameworks */, 88445B4219E1B50B0014F889 /* CoreLocation.framework in Frameworks */, 88445B3719E0AE5C0014F889 /* QuartzCore.framework in Frameworks */, 88445B3519E0AE4A0014F889 /* CoreGraphics.framework in Frameworks */, 88445B3319E0AE450014F889 /* Foundation.framework in Frameworks */, 88445B3119E0AE3F0014F889 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 88A25F1819D8DEC400924534 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 88445B4619E1B5290014F889 /* MapKit.framework in Frameworks */, 88445B4519E1B5210014F889 /* CoreLocation.framework in Frameworks */, 88445B3D19E0C0BE0014F889 /* QuartzCore.framework in Frameworks */, 88445B3C19E0C0B80014F889 /* CoreGraphics.framework in Frameworks */, 88445B3B19E0C0B10014F889 /* XCTest.framework in Frameworks */, 88445B3919E0C0AC0014F889 /* Foundation.framework in Frameworks */, 88445B3819E0C0A70014F889 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 636A8663AEEE5C37B65C515D /* Frameworks */ = { isa = PBXGroup; children = ( 1F0EFE0E1AC23D7E003FF3DB /* MobileCoreServices.framework */, 88445B3419E0AE4A0014F889 /* CoreGraphics.framework */, 88445B4119E1B50B0014F889 /* CoreLocation.framework */, 88445B3219E0AE450014F889 /* Foundation.framework */, 88445B4319E1B5110014F889 /* MapKit.framework */, 88445B3619E0AE5C0014F889 /* QuartzCore.framework */, 88445B3019E0AE3F0014F889 /* UIKit.framework */, 88445B3A19E0C0B10014F889 /* XCTest.framework */, ); name = Frameworks; sourceTree = ""; }; 88A25EF919D8DEC400924534 = { isa = PBXGroup; children = ( 886C33FE19F45E30006B4997 /* JSQMessagesViewController.podspec */, 88A25F3E19D8E01A00924534 /* JSQMessagesViewController */, 636A8663AEEE5C37B65C515D /* Frameworks */, 88A25F2B19D8DF2500924534 /* JSQMessagesDemo */, 88A25F1E19D8DEC500924534 /* JSQMessagesTests */, 88A25F0319D8DEC400924534 /* Products */, ); sourceTree = ""; }; 88A25F0319D8DEC400924534 /* Products */ = { isa = PBXGroup; children = ( 88A25F0219D8DEC400924534 /* JSQMessages.app */, 88A25F1B19D8DEC500924534 /* JSQMessagesTests.xctest */, ); name = Products; sourceTree = ""; }; 88A25F1E19D8DEC500924534 /* JSQMessagesTests */ = { isa = PBXGroup; children = ( 88A25FE219D8E18400924534 /* CategoryTests */, 88A25FE719D8E18400924534 /* ControllerTests */, 88A25FEA19D8E18400924534 /* FactoryTests */, 88A25FEF19D8E18400924534 /* LayoutTests */, 88A25FF219D8E18400924534 /* ModelTests */, 88A25FF819D8E18400924534 /* ViewTests */, 88A25F1F19D8DEC500924534 /* Supporting Files */, ); path = JSQMessagesTests; sourceTree = ""; }; 88A25F1F19D8DEC500924534 /* Supporting Files */ = { isa = PBXGroup; children = ( 88A25F2019D8DEC500924534 /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; 88A25F2B19D8DF2500924534 /* JSQMessagesDemo */ = { isa = PBXGroup; children = ( 88A25F2C19D8DF2500924534 /* AppDelegate.h */, 88A25F2D19D8DF2500924534 /* AppDelegate.m */, 88A25FD919D8E0C400924534 /* DemoMessagesViewController.h */, 88A25FDA19D8E0C400924534 /* DemoMessagesViewController.m */, 88A25FDB19D8E0C400924534 /* DemoModelData.h */, 88A25FDC19D8E0C400924534 /* DemoModelData.m */, 8885734819DE540400E89D20 /* DemoSettingsViewController.h */, 8885734919DE540400E89D20 /* DemoSettingsViewController.m */, 88A25F3219D8DF2500924534 /* Images.xcassets */, 88A2601A19D8E45600924534 /* Info.plist */, 54271E3A1C90469100294290 /* jsq_messages_sample.m4a */, 88A25F3419D8DF2500924534 /* main.m */, 88A25F3019D8DF2500924534 /* Main.storyboard */, 8885734B19DE55D000E89D20 /* NSUserDefaults+DemoSettings.h */, 8885734C19DE55D000E89D20 /* NSUserDefaults+DemoSettings.m */, 88A25FDD19D8E0C400924534 /* TableViewController.h */, 88A25FDE19D8E0C400924534 /* TableViewController.m */, 50B7F5AA1CA401FA009A44F5 /* Localizable.strings */, ); path = JSQMessagesDemo; sourceTree = ""; }; 88A25F3E19D8E01A00924534 /* JSQMessagesViewController */ = { isa = PBXGroup; children = ( 88A25F3F19D8E01A00924534 /* Assets */, 88A25F5419D8E01A00924534 /* Categories */, 88A25F5F19D8E01A00924534 /* Controllers */, 88A25F6519D8E01A00924534 /* Factories */, 88A25F6E19D8E01A00924534 /* JSQMessages.h */, 88A25F6F19D8E01A00924534 /* Layout */, 88A25F7619D8E01A00924534 /* Model */, 88A25F8919D8E01A00924534 /* Views */, ); path = JSQMessagesViewController; sourceTree = ""; }; 88A25F3F19D8E01A00924534 /* Assets */ = { isa = PBXGroup; children = ( 8861666C19F492B70025B958 /* JSQMessagesAssets.bundle */, ); path = Assets; sourceTree = ""; }; 88A25F5419D8E01A00924534 /* Categories */ = { isa = PBXGroup; children = ( 8873B60A1AB7B244006DF9AC /* NSBundle+JSQMessages.h */, 8873B60B1AB7B244006DF9AC /* NSBundle+JSQMessages.m */, 88A25F5719D8E01A00924534 /* NSString+JSQMessages.h */, 88A25F5819D8E01A00924534 /* NSString+JSQMessages.m */, 88A25F5919D8E01A00924534 /* UIColor+JSQMessages.h */, 88A25F5A19D8E01A00924534 /* UIColor+JSQMessages.m */, 88A25F5B19D8E01A00924534 /* UIImage+JSQMessages.h */, 88A25F5C19D8E01A00924534 /* UIImage+JSQMessages.m */, 88A25F5D19D8E01A00924534 /* UIView+JSQMessages.h */, 88A25F5E19D8E01A00924534 /* UIView+JSQMessages.m */, ); path = Categories; sourceTree = ""; }; 88A25F5F19D8E01A00924534 /* Controllers */ = { isa = PBXGroup; children = ( 88A25F6219D8E01A00924534 /* JSQMessagesViewController.h */, 88A25F6319D8E01A00924534 /* JSQMessagesViewController.m */, 88A25F6419D8E01A00924534 /* JSQMessagesViewController.xib */, ); path = Controllers; sourceTree = ""; }; 88A25F6519D8E01A00924534 /* Factories */ = { isa = PBXGroup; children = ( 88A25F6619D8E01A00924534 /* JSQMessagesAvatarImageFactory.h */, 88A25F6719D8E01A00924534 /* JSQMessagesAvatarImageFactory.m */, 88A25F6819D8E01A00924534 /* JSQMessagesBubbleImageFactory.h */, 88A25F6919D8E01A00924534 /* JSQMessagesBubbleImageFactory.m */, 88C4582E19F5F7A0008FD427 /* JSQMessagesMediaViewBubbleImageMasker.h */, 88C4582F19F5F7A0008FD427 /* JSQMessagesMediaViewBubbleImageMasker.m */, 88A25F6A19D8E01A00924534 /* JSQMessagesTimestampFormatter.h */, 88A25F6B19D8E01A00924534 /* JSQMessagesTimestampFormatter.m */, 88A25F6C19D8E01A00924534 /* JSQMessagesToolbarButtonFactory.h */, 88A25F6D19D8E01A00924534 /* JSQMessagesToolbarButtonFactory.m */, A04B0EBD1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.h */, A04B0EBE1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.m */, ); path = Factories; sourceTree = ""; }; 88A25F6F19D8E01A00924534 /* Layout */ = { isa = PBXGroup; children = ( 544A321F1CB2EE380084BFC0 /* JSQAudioMediaViewAttributes.h */, 544A32201CB2EE380084BFC0 /* JSQAudioMediaViewAttributes.m */, 88B5C4201B7C424700EC79D4 /* JSQMessagesBubbleSizeCalculating.h */, 88B5C41D1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.h */, 88B5C41E1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m */, 88A25F7019D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.h */, 88A25F7119D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.m */, 88A25F7219D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.h */, 88A25F7319D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m */, 88A25F7419D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.h */, 88A25F7519D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.m */, ); path = Layout; sourceTree = ""; }; 88A25F7619D8E01A00924534 /* Model */ = { isa = PBXGroup; children = ( 54271E3C1C905B9200294290 /* JSQAudioMediaItem.h */, 54271E3D1C905B9200294290 /* JSQAudioMediaItem.m */, 88445B3E19E1B4470014F889 /* JSQLocationMediaItem.h */, 88445B3F19E1B4470014F889 /* JSQLocationMediaItem.m */, 88A901B419F618B100F99777 /* JSQMediaItem.h */, 88A901B519F618B100F99777 /* JSQMediaItem.m */, 88A25F7919D8E01A00924534 /* JSQMessage.h */, 88A25F7A19D8E01A00924534 /* JSQMessage.m */, 88A25F7B19D8E01A00924534 /* JSQMessageAvatarImageDataSource.h */, 88A25F7C19D8E01A00924534 /* JSQMessageBubbleImageDataSource.h */, 88A25F7D19D8E01A00924534 /* JSQMessageData.h */, 88A25F7E19D8E01A00924534 /* JSQMessageMediaData.h */, 88A25F7F19D8E01A00924534 /* JSQMessagesAvatarImage.h */, 88A25F8019D8E01A00924534 /* JSQMessagesAvatarImage.m */, 88A25F8119D8E01A00924534 /* JSQMessagesBubbleImage.h */, 88A25F8219D8E01A00924534 /* JSQMessagesBubbleImage.m */, 88A25F8319D8E01A00924534 /* JSQMessagesCollectionViewDataSource.h */, 88A25F8419D8E01A00924534 /* JSQMessagesCollectionViewDelegateFlowLayout.h */, 88A25F8519D8E01A00924534 /* JSQPhotoMediaItem.h */, 88A25F8619D8E01A00924534 /* JSQPhotoMediaItem.m */, 886C33FB19F4371E006B4997 /* JSQVideoMediaItem.h */, 886C33FC19F4371E006B4997 /* JSQVideoMediaItem.m */, 58620BCC6ABA99E3C6FD36F5 /* JSQMessagesViewAccessoryButtonDelegate.h */, ); path = Model; sourceTree = ""; }; 88A25F8919D8E01A00924534 /* Views */ = { isa = PBXGroup; children = ( 883C11761A09FB100092A16D /* JSQMessagesCellTextView.h */, 883C11771A09FB100092A16D /* JSQMessagesCellTextView.m */, 88A25F8A19D8E01A00924534 /* JSQMessagesCollectionView.h */, 88A25F8B19D8E01A00924534 /* JSQMessagesCollectionView.m */, 88A25F8C19D8E01A00924534 /* JSQMessagesCollectionViewCell.h */, 88A25F8D19D8E01A00924534 /* JSQMessagesCollectionViewCell.m */, 88A25F8E19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.h */, 88A25F8F19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.m */, 88A25F9019D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.xib */, 88A25F9119D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.h */, 88A25F9219D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.m */, 88A25F9319D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.xib */, 88A25F9419D8E01A00924534 /* JSQMessagesComposerTextView.h */, 88A25F9519D8E01A00924534 /* JSQMessagesComposerTextView.m */, 88A25F9619D8E01A00924534 /* JSQMessagesInputToolbar.h */, 88A25F9719D8E01A00924534 /* JSQMessagesInputToolbar.m */, 88A25F9819D8E01A00924534 /* JSQMessagesLabel.h */, 88A25F9919D8E01A00924534 /* JSQMessagesLabel.m */, 88A25F9A19D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.h */, 88A25F9B19D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.m */, 88A25F9C19D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.xib */, 88078A9B19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.h */, 88078A9C19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.m */, 88A25F9D19D8E01A00924534 /* JSQMessagesToolbarContentView.h */, 88A25F9E19D8E01A00924534 /* JSQMessagesToolbarContentView.m */, 88A25F9F19D8E01A00924534 /* JSQMessagesToolbarContentView.xib */, 88A25FA019D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.h */, 88A25FA119D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.m */, 88A25FA219D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.xib */, BF10D6A81D062AD10072D215 /* JSQMessagesTypingView.h */, BF10D6A91D062AD10072D215 /* JSQMessagesTypingView.m */, ); path = Views; sourceTree = ""; }; 88A25FE219D8E18400924534 /* CategoryTests */ = { isa = PBXGroup; children = ( 8873B60D1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m */, 88A25FE319D8E18400924534 /* JSQMessagesNSStringTests.m */, 88A25FE419D8E18400924534 /* JSQMessagesUIColorTests.m */, 88A25FE519D8E18400924534 /* JSQMessagesUIImageTests.m */, 88A25FE619D8E18400924534 /* JSQMessagesUIViewTests.m */, ); path = CategoryTests; sourceTree = ""; }; 88A25FE719D8E18400924534 /* ControllerTests */ = { isa = PBXGroup; children = ( 88A25FE919D8E18400924534 /* JSQMessagesViewControllerTests.m */, ); path = ControllerTests; sourceTree = ""; }; 88A25FEA19D8E18400924534 /* FactoryTests */ = { isa = PBXGroup; children = ( 88A25FEB19D8E18400924534 /* JSQMessagesAvatarImageFactoryTests.m */, 88A25FEC19D8E18400924534 /* JSQMessagesBubbleImageFactoryTests.m */, 88324C3319F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m */, 88A25FED19D8E18400924534 /* JSQMessagesTimestampFormatterTests.m */, 88A25FEE19D8E18400924534 /* JSQMessagesToolbarButtonFactoryTests.m */, ); path = FactoryTests; sourceTree = ""; }; 88A25FEF19D8E18400924534 /* LayoutTests */ = { isa = PBXGroup; children = ( 88A25FF019D8E18400924534 /* JSQMessagesCollectionViewFlowLayoutTests.m */, 88A25FF119D8E18400924534 /* JSQMessagesCollectionViewLayoutAttributesTests.m */, ); path = LayoutTests; sourceTree = ""; }; 88A25FF219D8E18400924534 /* ModelTests */ = { isa = PBXGroup; children = ( 54271E3F1C905D1600294290 /* JSQAudioMediaItemTests.m */, 88C00A4D1A44D4C600B004B3 /* JSQLocationMediaItemTests.m */, 88A25FF319D8E18400924534 /* JSQMessageMediaTests.m */, 88A25FF419D8E18400924534 /* JSQMessagesAvatarImageTests.m */, 88A25FF519D8E18400924534 /* JSQMessagesBubbleImageTests.m */, 88A25FF619D8E18400924534 /* JSQMessageTextTests.m */, 88C00A4F1A44D4D800B004B3 /* JSQPhotoMediaItemTests.m */, 88C00A511A44D4E500B004B3 /* JSQVideoMediaItemTests.m */, ); path = ModelTests; sourceTree = ""; }; 88A25FF819D8E18400924534 /* ViewTests */ = { isa = PBXGroup; children = ( 88A25FF919D8E18400924534 /* JSQMessagesCollectionViewCellTests.m */, 88A25FFA19D8E18400924534 /* JSQMessagesCollectionViewTests.m */, 88A25FFB19D8E18400924534 /* JSQMessagesComposerTextViewTests.m */, 88A25FFC19D8E18400924534 /* JSQMessagesInputToolbarTests.m */, 88A25FFD19D8E18400924534 /* JSQMessagesLabelTests.m */, 88A25FFE19D8E18400924534 /* JSQMessagesLoadEarlierHeaderViewTests.m */, 88A25FFF19D8E18400924534 /* JSQMessagesToolbarContentViewTests.m */, 88A2600019D8E18400924534 /* JSQMessagesTypingIndicatorFooterViewTests.m */, ); path = ViewTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 88A25F0119D8DEC400924534 /* JSQMessages */ = { isa = PBXNativeTarget; buildConfigurationList = 88A25F2519D8DEC500924534 /* Build configuration list for PBXNativeTarget "JSQMessages" */; buildPhases = ( 88A25EFE19D8DEC400924534 /* Sources */, 88A25EFF19D8DEC400924534 /* Frameworks */, 88A25F0019D8DEC400924534 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = JSQMessages; productName = JSQMessages; productReference = 88A25F0219D8DEC400924534 /* JSQMessages.app */; productType = "com.apple.product-type.application"; }; 88A25F1A19D8DEC400924534 /* JSQMessagesTests */ = { isa = PBXNativeTarget; buildConfigurationList = 88A25F2819D8DEC500924534 /* Build configuration list for PBXNativeTarget "JSQMessagesTests" */; buildPhases = ( 88A25F1719D8DEC400924534 /* Sources */, 88A25F1819D8DEC400924534 /* Frameworks */, 88A25F1919D8DEC400924534 /* Resources */, ); buildRules = ( ); dependencies = ( 88A25F1D19D8DEC500924534 /* PBXTargetDependency */, ); name = JSQMessagesTests; productName = JSQMessagesTests; productReference = 88A25F1B19D8DEC500924534 /* JSQMessagesTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 88A25EFA19D8DEC400924534 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0800; ORGANIZATIONNAME = "Hexed Bits"; TargetAttributes = { 88A25F0119D8DEC400924534 = { CreatedOnToolsVersion = 6.0.1; }; 88A25F1A19D8DEC400924534 = { CreatedOnToolsVersion = 6.0.1; TestTargetID = 88A25F0119D8DEC400924534; }; }; }; buildConfigurationList = 88A25EFD19D8DEC400924534 /* Build configuration list for PBXProject "JSQMessages" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, es, de, "zh-Hans", "zh-Hant", ro, ru, pl, pt, fr, it, he, nl, tr, ); mainGroup = 88A25EF919D8DEC400924534; productRefGroup = 88A25F0319D8DEC400924534 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 88A25F0119D8DEC400924534 /* JSQMessages */, 88A25F1A19D8DEC400924534 /* JSQMessagesTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 88A25F0019D8DEC400924534 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 886C33FF19F45E30006B4997 /* JSQMessagesViewController.podspec in Resources */, 8861666D19F492B70025B958 /* JSQMessagesAssets.bundle in Resources */, 50B7F5A81CA401FA009A44F5 /* Localizable.strings in Resources */, 88A25FCF19D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.xib in Resources */, 88A25FCD19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.xib in Resources */, 88A25FD619D8E01A00924534 /* JSQMessagesToolbarContentView.xib in Resources */, 88A25F3A19D8DF2500924534 /* Images.xcassets in Resources */, 54271E3B1C90469100294290 /* jsq_messages_sample.m4a in Resources */, 88A25FBC19D8E01A00924534 /* JSQMessagesViewController.xib in Resources */, 88A25FD819D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.xib in Resources */, 88A25F3919D8DF2500924534 /* Main.storyboard in Resources */, 88A25FD419D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88A25F1919D8DEC400924534 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 88A2601B19D8E45600924534 /* Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 88A25EFE19D8DEC400924534 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88078A9D19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.m in Sources */, 88A25FCE19D8E01A00924534 /* JSQMessagesCollectionViewCellOutgoing.m in Sources */, 88A25FD119D8E01A00924534 /* JSQMessagesInputToolbar.m in Sources */, 88A25FCB19D8E01A00924534 /* JSQMessagesCollectionViewCell.m in Sources */, 88A25FBB19D8E01A00924534 /* JSQMessagesViewController.m in Sources */, 8885734D19DE55D000E89D20 /* NSUserDefaults+DemoSettings.m in Sources */, A04B0EBF1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.m in Sources */, 544A32211CB2EE380084BFC0 /* JSQAudioMediaViewAttributes.m in Sources */, 883C11781A09FB100092A16D /* JSQMessagesCellTextView.m in Sources */, 88A25FB919D8E01A00924534 /* UIView+JSQMessages.m in Sources */, 88A25FCA19D8E01A00924534 /* JSQMessagesCollectionView.m in Sources */, 88A25FD219D8E01A00924534 /* JSQMessagesLabel.m in Sources */, 88445B4019E1B4470014F889 /* JSQLocationMediaItem.m in Sources */, 88A25FC619D8E01A00924534 /* JSQMessagesAvatarImage.m in Sources */, 88A25FD519D8E01A00924534 /* JSQMessagesToolbarContentView.m in Sources */, 88A25FC119D8E01A00924534 /* JSQMessagesCollectionViewFlowLayout.m in Sources */, 8885734A19DE540400E89D20 /* DemoSettingsViewController.m in Sources */, 88A25FC719D8E01A00924534 /* JSQMessagesBubbleImage.m in Sources */, 88A25FC519D8E01A00924534 /* JSQMessage.m in Sources */, 88A25FD719D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.m in Sources */, 88A25FD319D8E01A00924534 /* JSQMessagesLoadEarlierHeaderView.m in Sources */, 88A25FC819D8E01A00924534 /* JSQPhotoMediaItem.m in Sources */, 88C4583019F5F7A0008FD427 /* JSQMessagesMediaViewBubbleImageMasker.m in Sources */, 88A25FB819D8E01A00924534 /* UIImage+JSQMessages.m in Sources */, 88A25FBF19D8E01A00924534 /* JSQMessagesTimestampFormatter.m in Sources */, 88A25FE019D8E0C400924534 /* DemoModelData.m in Sources */, 88A25F3C19D8DF2500924534 /* main.m in Sources */, 88A25F3719D8DF2500924534 /* AppDelegate.m in Sources */, 54271E3E1C905B9200294290 /* JSQAudioMediaItem.m in Sources */, 88B5C41F1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m in Sources */, BF10D6AA1D062AD10072D215 /* JSQMessagesTypingView.m in Sources */, 88A25FB619D8E01A00924534 /* NSString+JSQMessages.m in Sources */, 88A901B619F618B100F99777 /* JSQMediaItem.m in Sources */, 88A25FCC19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.m in Sources */, 88A25FBE19D8E01A00924534 /* JSQMessagesBubbleImageFactory.m in Sources */, 88A25FDF19D8E0C400924534 /* DemoMessagesViewController.m in Sources */, 88A25FB719D8E01A00924534 /* UIColor+JSQMessages.m in Sources */, 886C33FD19F4371E006B4997 /* JSQVideoMediaItem.m in Sources */, 88A25FC019D8E01A00924534 /* JSQMessagesToolbarButtonFactory.m in Sources */, 88A25FC219D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m in Sources */, 88A25FE119D8E0C400924534 /* TableViewController.m in Sources */, 88A25FBD19D8E01A00924534 /* JSQMessagesAvatarImageFactory.m in Sources */, 8873B60C1AB7B244006DF9AC /* NSBundle+JSQMessages.m in Sources */, 88A25FD019D8E01A00924534 /* JSQMessagesComposerTextView.m in Sources */, 88A25FC319D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 88A25F1719D8DEC400924534 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 88A2600219D8E18400924534 /* JSQMessagesUIColorTests.m in Sources */, 88A2601819D8E18400924534 /* JSQMessagesToolbarContentViewTests.m in Sources */, 88C00A521A44D4E500B004B3 /* JSQVideoMediaItemTests.m in Sources */, 88A2601519D8E18400924534 /* JSQMessagesInputToolbarTests.m in Sources */, 88A2601719D8E18400924534 /* JSQMessagesLoadEarlierHeaderViewTests.m in Sources */, 54271E401C905D1600294290 /* JSQAudioMediaItemTests.m in Sources */, 88A2601219D8E18400924534 /* JSQMessagesCollectionViewCellTests.m in Sources */, 88A2601619D8E18400924534 /* JSQMessagesLabelTests.m in Sources */, 88A2600B19D8E18400924534 /* JSQMessagesCollectionViewFlowLayoutTests.m in Sources */, 88A2601019D8E18400924534 /* JSQMessageTextTests.m in Sources */, 88A2600D19D8E18400924534 /* JSQMessageMediaTests.m in Sources */, 88A2600719D8E18400924534 /* JSQMessagesAvatarImageFactoryTests.m in Sources */, 88A2600419D8E18400924534 /* JSQMessagesUIViewTests.m in Sources */, 88A2600F19D8E18400924534 /* JSQMessagesBubbleImageTests.m in Sources */, 88A2600E19D8E18400924534 /* JSQMessagesAvatarImageTests.m in Sources */, 88A2600919D8E18400924534 /* JSQMessagesTimestampFormatterTests.m in Sources */, 88A2601419D8E18400924534 /* JSQMessagesComposerTextViewTests.m in Sources */, 88A2601319D8E18400924534 /* JSQMessagesCollectionViewTests.m in Sources */, 88A2600119D8E18400924534 /* JSQMessagesNSStringTests.m in Sources */, 88A2600A19D8E18400924534 /* JSQMessagesToolbarButtonFactoryTests.m in Sources */, 88C00A501A44D4D800B004B3 /* JSQPhotoMediaItemTests.m in Sources */, 88324C3419F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m in Sources */, 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 */, 88A2600819D8E18400924534 /* JSQMessagesBubbleImageFactoryTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 88A25F1D19D8DEC500924534 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 88A25F0119D8DEC400924534 /* JSQMessages */; targetProxy = 88A25F1C19D8DEC500924534 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 50B7F5AA1CA401FA009A44F5 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 50B7F5A91CA401FA009A44F5 /* he */, 50B7F5AB1CA40202009A44F5 /* Base */, ); name = Localizable.strings; sourceTree = ""; }; 88A25F3019D8DF2500924534 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 88A25F3119D8DF2500924534 /* Base */, 50B7F5A51CA3FF4E009A44F5 /* he */, ); name = Main.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 88A25F2319D8DEC500924534 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 88A25F2419D8DEC500924534 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 88A25F2619D8DEC500924534 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/JSQMessagesDemo/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.hexedbits.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; }; name = Debug; }; 88A25F2719D8DEC500924534 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/JSQMessagesDemo/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.hexedbits.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; }; name = Release; }; 88A25F2919D8DEC500924534 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = JSQMessagesTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.hexedbits.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JSQMessages.app/JSQMessages"; }; name = Debug; }; 88A25F2A19D8DEC500924534 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = JSQMessagesTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.hexedbits.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JSQMessages.app/JSQMessages"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 88A25EFD19D8DEC400924534 /* Build configuration list for PBXProject "JSQMessages" */ = { isa = XCConfigurationList; buildConfigurations = ( 88A25F2319D8DEC500924534 /* Debug */, 88A25F2419D8DEC500924534 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88A25F2519D8DEC500924534 /* Build configuration list for PBXNativeTarget "JSQMessages" */ = { isa = XCConfigurationList; buildConfigurations = ( 88A25F2619D8DEC500924534 /* Debug */, 88A25F2719D8DEC500924534 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 88A25F2819D8DEC500924534 /* Build configuration list for PBXNativeTarget "JSQMessagesTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 88A25F2919D8DEC500924534 /* Debug */, 88A25F2A19D8DEC500924534 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 88A25EFA19D8DEC400924534 /* Project object */; } ================================================ FILE: JSQMessages.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: JSQMessages.xcodeproj/xcshareddata/xcschemes/JSQMessages.xcscheme ================================================ ================================================ FILE: JSQMessages.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: JSQMessagesDemo/AppDelegate.h ================================================ // // 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 #import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: JSQMessagesDemo/AppDelegate.m ================================================ // // 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 "AppDelegate.h" #import "NSUserDefaults+DemoSettings.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Load our default settings [NSUserDefaults saveIncomingAvatarSetting:YES]; [NSUserDefaults saveOutgoingAvatarSetting:YES]; return YES; } @end ================================================ FILE: JSQMessagesDemo/Base.lproj/Localizable.strings ================================================ // // 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 // "Media messages" = "Media messages"; "Cancel" = "Cancel"; "Send photo" = "Send photo"; "Send location" = "Send location"; "Send video" = "Send video"; "Send video thumbnail" = "Send video with thumbnail"; "Custom Action" = "Custom Action"; "OK" = "OK"; "Welcome to JSQMessages: A messaging UI framework for iOS." = "Welcome to JSQMessages: A messaging UI framework for iOS."; "It is simple, elegant, and easy to use. There are super sweet default settings, but you can customize like crazy." = "It is simple, elegant, and easy to use. There are super sweet default settings, but you can customize like crazy."; "It even has data detectors. You can call me tonight. My cell number is 123-456-7890. My website is www.hexedbits.com." = "It even has data detectors. You can call me tonight. My cell number is 123-456-7890. My website is www.hexedbits.com."; "JSQMessagesViewController is nearly an exact replica of the iOS Messages App. And perhaps, better." = "JSQMessagesViewController is nearly an exact replica of the iOS Messages App. And perhaps, better."; "It is unit-tested, free, open-source, and documented." = "It is unit-tested, free, open-source, and documented."; "Now with media messages!" = "Now with media messages!"; ================================================ FILE: JSQMessagesDemo/Base.lproj/Main.storyboard ================================================ ================================================ FILE: JSQMessagesDemo/DemoMessagesViewController.h ================================================ // // 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 all the things #import "JSQMessages.h" #import "DemoModelData.h" #import "NSUserDefaults+DemoSettings.h" @class DemoMessagesViewController; @protocol JSQDemoViewControllerDelegate - (void)didDismissJSQDemoViewController:(DemoMessagesViewController *)vc; @end @interface DemoMessagesViewController : JSQMessagesViewController @property (weak, nonatomic) id delegateModal; @property (strong, nonatomic) DemoModelData *demoData; - (void)receiveMessagePressed:(UIBarButtonItem *)sender; - (void)closePressed:(UIBarButtonItem *)sender; @end ================================================ FILE: JSQMessagesDemo/DemoMessagesViewController.m ================================================ // // 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 "DemoMessagesViewController.h" #import "JSQMessagesViewAccessoryButtonDelegate.h" @interface DemoMessagesViewController () @end @implementation DemoMessagesViewController #pragma mark - View lifecycle /** * Override point for customization. * * Customize your view. * Look at the properties on `JSQMessagesViewController` and `JSQMessagesCollectionView` to see what is possible. * * Customize your layout. * Look at the properties on `JSQMessagesCollectionViewFlowLayout` to see what is possible. */ - (void)viewDidLoad { [super viewDidLoad]; self.title = @"JSQMessages"; self.inputToolbar.contentView.textView.pasteDelegate = self; /** * Load up our fake data for the demo */ self.demoData = [[DemoModelData alloc] init]; /** * Set up message accessory button delegate and configuration */ self.collectionView.accessoryDelegate = self; /** * You can set custom avatar sizes */ if (![NSUserDefaults incomingAvatarSetting]) { self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero; } if (![NSUserDefaults outgoingAvatarSetting]) { self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero; } self.showLoadEarlierMessagesHeader = YES; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage jsq_defaultTypingIndicatorImage] style:UIBarButtonItemStylePlain target:self action:@selector(receiveMessagePressed:)]; /** * Register custom menu actions for cells. */ [JSQMessagesCollectionViewCell registerMenuAction:@selector(customAction:)]; /** * OPT-IN: allow cells to be deleted */ [JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)]; /** * 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 { [super viewWillAppear:animated]; if (self.delegateModal) { self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop target:self action:@selector(closePressed:)]; } } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; /** * Enable/disable springy bubbles, default is NO. * You must set this from `viewDidAppear:` * Note: this feature is mostly stable, but still experimental */ self.collectionView.collectionViewLayout.springinessEnabled = [NSUserDefaults springinessSetting]; } #pragma mark - Custom menu actions for cells - (void)didReceiveMenuWillShowNotification:(NSNotification *)notification { /** * Display custom menu actions for cells. */ UIMenuController *menu = [notification object]; menu.menuItems = @[ [[UIMenuItem alloc] initWithTitle:@"Custom Action" action:@selector(customAction:)] ]; [super didReceiveMenuWillShowNotification:notification]; } #pragma mark - Testing - (void)pushMainViewController { UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; UINavigationController *nc = [sb instantiateInitialViewController]; [self.navigationController pushViewController:nc.topViewController animated:YES]; } #pragma mark - Actions - (void)receiveMessagePressed:(UIBarButtonItem *)sender { /** * DEMO ONLY * * The following is simply to simulate received messages for the demo. * Do not actually do this. */ /** * Show the typing indicator to be shown */ self.showTypingIndicator = !self.showTypingIndicator; /** * Scroll to actually view the indicator */ [self scrollToBottomAnimated:YES]; /** * Copy last sent message, this will be the new "received" message */ JSQMessage *copyMessage = [[self.demoData.messages lastObject] copy]; if (!copyMessage) { copyMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdJobs displayName:kJSQDemoAvatarDisplayNameJobs text:@"First received!"]; } /** * Allow typing indicator to show */ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSMutableArray *userIds = [[self.demoData.users allKeys] mutableCopy]; [userIds removeObject:self.senderId]; NSString *randomUserId = userIds[arc4random_uniform((int)[userIds count])]; JSQMessage *newMessage = nil; id newMediaData = nil; id newMediaAttachmentCopy = nil; if (copyMessage.isMediaMessage) { /** * Last message was a media message */ id copyMediaData = copyMessage.media; if ([copyMediaData isKindOfClass:[JSQPhotoMediaItem class]]) { JSQPhotoMediaItem *photoItemCopy = [((JSQPhotoMediaItem *)copyMediaData) copy]; photoItemCopy.appliesMediaViewMaskAsOutgoing = NO; newMediaAttachmentCopy = [UIImage imageWithCGImage:photoItemCopy.image.CGImage]; /** * Set image to nil to simulate "downloading" the image * and show the placeholder view */ photoItemCopy.image = nil; newMediaData = photoItemCopy; } else if ([copyMediaData isKindOfClass:[JSQLocationMediaItem class]]) { JSQLocationMediaItem *locationItemCopy = [((JSQLocationMediaItem *)copyMediaData) copy]; locationItemCopy.appliesMediaViewMaskAsOutgoing = NO; newMediaAttachmentCopy = [locationItemCopy.location copy]; /** * Set location to nil to simulate "downloading" the location data */ locationItemCopy.location = nil; newMediaData = locationItemCopy; } else if ([copyMediaData isKindOfClass:[JSQVideoMediaItem class]]) { JSQVideoMediaItem *videoItemCopy = [((JSQVideoMediaItem *)copyMediaData) copy]; videoItemCopy.appliesMediaViewMaskAsOutgoing = NO; newMediaAttachmentCopy = [videoItemCopy.fileURL copy]; /** * Reset video item to simulate "downloading" the video */ videoItemCopy.fileURL = nil; videoItemCopy.isReadyToPlay = NO; newMediaData = videoItemCopy; } else if ([copyMediaData isKindOfClass:[JSQAudioMediaItem class]]) { JSQAudioMediaItem *audioItemCopy = [((JSQAudioMediaItem *)copyMediaData) copy]; audioItemCopy.appliesMediaViewMaskAsOutgoing = NO; newMediaAttachmentCopy = [audioItemCopy.audioData copy]; /** * Reset audio item to simulate "downloading" the audio */ audioItemCopy.audioData = nil; newMediaData = audioItemCopy; } else { NSLog(@"%s error: unrecognized media item", __PRETTY_FUNCTION__); } newMessage = [JSQMessage messageWithSenderId:randomUserId displayName:self.demoData.users[randomUserId] media:newMediaData]; } else { /** * Last message was a text message */ newMessage = [JSQMessage messageWithSenderId:randomUserId displayName:self.demoData.users[randomUserId] text:copyMessage.text]; } /** * Upon receiving a message, you should: * * 1. Play sound (optional) * 2. Add new id object to your data source * 3. Call `finishReceivingMessage` */ // [JSQSystemSoundPlayer jsq_playMessageReceivedSound]; [self.demoData.messages addObject:newMessage]; [self finishReceivingMessageAnimated:YES]; if (newMessage.isMediaMessage) { /** * Simulate "downloading" media */ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ /** * Media is "finished downloading", re-display visible cells * * If media cell is not visible, the next time it is dequeued the view controller will display its new attachment data * * Reload the specific item, or simply call `reloadData` */ if ([newMediaData isKindOfClass:[JSQPhotoMediaItem class]]) { ((JSQPhotoMediaItem *)newMediaData).image = newMediaAttachmentCopy; [self.collectionView reloadData]; } else if ([newMediaData isKindOfClass:[JSQLocationMediaItem class]]) { [((JSQLocationMediaItem *)newMediaData)setLocation:newMediaAttachmentCopy withCompletionHandler:^{ [self.collectionView reloadData]; }]; } else if ([newMediaData isKindOfClass:[JSQVideoMediaItem class]]) { ((JSQVideoMediaItem *)newMediaData).fileURL = newMediaAttachmentCopy; ((JSQVideoMediaItem *)newMediaData).isReadyToPlay = YES; [self.collectionView reloadData]; } else if ([newMediaData isKindOfClass:[JSQAudioMediaItem class]]) { ((JSQAudioMediaItem *)newMediaData).audioData = newMediaAttachmentCopy; [self.collectionView reloadData]; } else { NSLog(@"%s error: unrecognized media item", __PRETTY_FUNCTION__); } }); } }); } - (void)closePressed:(UIBarButtonItem *)sender { [self.delegateModal didDismissJSQDemoViewController:self]; } #pragma mark - JSQMessagesViewController method overrides - (void)didPressSendButton:(UIButton *)button withMessageText:(NSString *)text senderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date { /** * Sending a message. Your implementation of this method should do *at least* the following: * * 1. Play sound (optional) * 2. Add new id object to your data source * 3. Call `finishSendingMessage` */ // [JSQSystemSoundPlayer jsq_playMessageSentSound]; JSQMessage *message = [[JSQMessage alloc] initWithSenderId:senderId senderDisplayName:senderDisplayName date:date text:text]; [self.demoData.messages addObject:message]; [self finishSendingMessageAnimated:YES]; } - (void)didPressAccessoryButton:(UIButton *)sender { [self.inputToolbar.contentView.textView resignFirstResponder]; UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"Media messages", nil) delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel", nil) destructiveButtonTitle:nil otherButtonTitles:NSLocalizedString(@"Send photo", nil), NSLocalizedString(@"Send location", nil), NSLocalizedString(@"Send video", nil), NSLocalizedString(@"Send video thumbnail", nil), NSLocalizedString(@"Send audio", nil), nil]; [sheet showFromToolbar:self.inputToolbar]; } - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { if (buttonIndex == actionSheet.cancelButtonIndex) { [self.inputToolbar.contentView.textView becomeFirstResponder]; return; } switch (buttonIndex) { case 0: [self.demoData addPhotoMediaMessage]; break; case 1: { __weak UICollectionView *weakView = self.collectionView; [self.demoData addLocationMediaMessageCompletion:^{ [weakView reloadData]; }]; } break; case 2: [self.demoData addVideoMediaMessage]; break; case 3: [self.demoData addVideoMediaMessageWithThumbnail]; break; case 4: [self.demoData addAudioMediaMessage]; break; } // [JSQSystemSoundPlayer jsq_playMessageSentSound]; [self finishSendingMessageAnimated:YES]; } #pragma mark - JSQMessages CollectionView DataSource - (NSString *)senderId { return kJSQDemoAvatarIdSquires; } - (NSString *)senderDisplayName { return kJSQDemoAvatarDisplayNameSquires; } - (id)collectionView:(JSQMessagesCollectionView *)collectionView messageDataForItemAtIndexPath:(NSIndexPath *)indexPath { return [self.demoData.messages objectAtIndex:indexPath.item]; } - (void)collectionView:(JSQMessagesCollectionView *)collectionView didDeleteMessageAtIndexPath:(NSIndexPath *)indexPath { [self.demoData.messages removeObjectAtIndex:indexPath.item]; } - (id)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath { /** * You may return nil here if you do not want bubbles. * In this case, you should set the background color of your collection view cell's textView. * * Otherwise, return your previously created bubble image data objects. */ JSQMessage *message = [self.demoData.messages objectAtIndex:indexPath.item]; if ([message.senderId isEqualToString:self.senderId]) { return self.demoData.outgoingBubbleImageData; } return self.demoData.incomingBubbleImageData; } - (id)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath { /** * Return `nil` here if you do not want avatars. * If you do return `nil`, be sure to do the following in `viewDidLoad`: * * self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero; * self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero; * * It is possible to have only outgoing avatars or only incoming avatars, too. */ /** * Return your previously created avatar image data objects. * * Note: these the avatars will be sized according to these values: * * self.collectionView.collectionViewLayout.incomingAvatarViewSize * self.collectionView.collectionViewLayout.outgoingAvatarViewSize * * Override the defaults in `viewDidLoad` */ JSQMessage *message = [self.demoData.messages objectAtIndex:indexPath.item]; if ([message.senderId isEqualToString:self.senderId]) { if (![NSUserDefaults outgoingAvatarSetting]) { return nil; } } else { if (![NSUserDefaults incomingAvatarSetting]) { return nil; } } return [self.demoData.avatars objectForKey:message.senderId]; } - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath { /** * This logic should be consistent with what you return from `heightForCellTopLabelAtIndexPath:` * The other label text delegate methods should follow a similar pattern. * * Show a timestamp for every 3rd message */ if (indexPath.item % 3 == 0) { JSQMessage *message = [self.demoData.messages objectAtIndex:indexPath.item]; return [[JSQMessagesTimestampFormatter sharedFormatter] attributedTimestampForDate:message.date]; } return nil; } - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath { JSQMessage *message = [self.demoData.messages objectAtIndex:indexPath.item]; /** * iOS7-style sender name labels */ if ([message.senderId isEqualToString:self.senderId]) { return nil; } if (indexPath.item - 1 > 0) { JSQMessage *previousMessage = [self.demoData.messages objectAtIndex:indexPath.item - 1]; if ([[previousMessage senderId] isEqualToString:message.senderId]) { return nil; } } /** * Don't specify attributes to use the defaults. */ return [[NSAttributedString alloc] initWithString:message.senderDisplayName]; } - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath { return nil; } #pragma mark - UICollectionView DataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return [self.demoData.messages count]; } - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { /** * Override point for customizing cells */ JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath]; /** * Configure almost *anything* on the cell * * Text colors, label text, label colors, etc. * * * DO NOT set `cell.textView.font` ! * Instead, you need to set `self.collectionView.collectionViewLayout.messageBubbleFont` to the font you want in `viewDidLoad` * * * DO NOT manipulate cell layout information! * Instead, override the properties you want on `self.collectionView.collectionViewLayout` from `viewDidLoad` */ JSQMessage *msg = [self.demoData.messages objectAtIndex:indexPath.item]; if (!msg.isMediaMessage) { if ([msg.senderId isEqualToString:self.senderId]) { cell.textView.textColor = [UIColor blackColor]; } else { cell.textView.textColor = [UIColor whiteColor]; } cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : cell.textView.textColor, NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) }; } cell.accessoryButton.hidden = ![self shouldShowAccessoryButtonForMessage:msg]; return cell; } - (BOOL)shouldShowAccessoryButtonForMessage:(id)message { return ([message isMediaMessage] && [NSUserDefaults accessoryButtonForMediaMessages]); } #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:NSLocalizedString(@"Custom Action", nil) message:nil delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] show]; } #pragma mark - JSQMessages collection view flow layout delegate #pragma mark - Adjusting cell label heights - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath { /** * Each label in a cell has a `height` delegate method that corresponds to its text dataSource method */ /** * This logic should be consistent with what you return from `attributedTextForCellTopLabelAtIndexPath:` * The other label height delegate methods should follow similarly * * Show a timestamp for every 3rd message */ if (indexPath.item % 3 == 0) { return kJSQMessagesCollectionViewCellLabelHeightDefault; } return 0.0f; } - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath { /** * iOS7-style sender name labels */ JSQMessage *currentMessage = [self.demoData.messages objectAtIndex:indexPath.item]; if ([[currentMessage senderId] isEqualToString:self.senderId]) { return 0.0f; } if (indexPath.item - 1 > 0) { JSQMessage *previousMessage = [self.demoData.messages objectAtIndex:indexPath.item - 1]; if ([[previousMessage senderId] isEqualToString:[currentMessage senderId]]) { return 0.0f; } } return kJSQMessagesCollectionViewCellLabelHeightDefault; } - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath { return 0.0f; } #pragma mark - Responding to collection view tap events - (void)collectionView:(JSQMessagesCollectionView *)collectionView header:(JSQMessagesLoadEarlierHeaderView *)headerView didTapLoadEarlierMessagesButton:(UIButton *)sender { NSLog(@"Load earlier messages!"); } - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapAvatarImageView:(UIImageView *)avatarImageView atIndexPath:(NSIndexPath *)indexPath { NSLog(@"Tapped avatar!"); } - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"Tapped message bubble!"); } - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCellAtIndexPath:(NSIndexPath *)indexPath touchLocation:(CGPoint)touchLocation { NSLog(@"Tapped cell at %@!", NSStringFromCGPoint(touchLocation)); } #pragma mark - JSQMessagesComposerTextViewPasteDelegate methods - (BOOL)composerTextView:(JSQMessagesComposerTextView *)textView shouldPasteWithSender:(id)sender { if ([UIPasteboard generalPasteboard].image) { // If there's an image in the pasteboard, construct a media item with that image and `send` it. JSQPhotoMediaItem *item = [[JSQPhotoMediaItem alloc] initWithImage:[UIPasteboard generalPasteboard].image]; JSQMessage *message = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:[NSDate date] media:item]; [self.demoData.messages addObject:message]; [self finishSendingMessage]; return NO; } return YES; } #pragma mark - JSQMessagesViewAccessoryDelegate methods - (void)messageView:(JSQMessagesCollectionView *)view didTapAccessoryButtonAtIndexPath:(NSIndexPath *)path { NSLog(@"Tapped accessory button!"); } @end ================================================ FILE: JSQMessagesDemo/DemoModelData.h ================================================ // // 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 #import #import #import "JSQMessages.h" /** * This is for demo/testing purposes only. * This object sets up some fake model data. * Do not actually do anything like this. */ static NSString * const kJSQDemoAvatarDisplayNameSquires = @"Jesse Squires"; static NSString * const kJSQDemoAvatarDisplayNameCook = @"Tim Cook"; static NSString * const kJSQDemoAvatarDisplayNameJobs = @"Jobs"; static NSString * const kJSQDemoAvatarDisplayNameWoz = @"Steve Wozniak"; static NSString * const kJSQDemoAvatarIdSquires = @"053496-4509-289"; static NSString * const kJSQDemoAvatarIdCook = @"468-768355-23123"; static NSString * const kJSQDemoAvatarIdJobs = @"707-8956784-57"; static NSString * const kJSQDemoAvatarIdWoz = @"309-41802-93823"; @interface DemoModelData : NSObject @property (strong, nonatomic) NSMutableArray *messages; @property (strong, nonatomic) NSDictionary *avatars; @property (strong, nonatomic) JSQMessagesBubbleImage *outgoingBubbleImageData; @property (strong, nonatomic) JSQMessagesBubbleImage *incomingBubbleImageData; @property (strong, nonatomic) NSDictionary *users; - (void)addPhotoMediaMessage; - (void)addLocationMediaMessageCompletion:(JSQLocationMediaItemCompletionBlock)completion; - (void)addVideoMediaMessage; - (void)addVideoMediaMessageWithThumbnail; - (void)addAudioMediaMessage; @end ================================================ FILE: JSQMessagesDemo/DemoModelData.m ================================================ // // 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 "DemoModelData.h" #import "NSUserDefaults+DemoSettings.h" /** * This is for demo/testing purposes only. * This object sets up some fake model data. * Do not actually do anything like this. */ @implementation DemoModelData - (instancetype)init { self = [super init]; if (self) { if ([NSUserDefaults emptyMessagesSetting]) { self.messages = [NSMutableArray new]; } else { [self loadFakeMessages]; } /** * Create avatar images once. * * Be sure to create your avatars one time and reuse them for good performance. * * If you are not using avatars, ignore this. */ JSQMessagesAvatarImageFactory *avatarFactory = [[JSQMessagesAvatarImageFactory alloc] initWithDiameter:kJSQMessagesCollectionViewAvatarSizeDefault]; JSQMessagesAvatarImage *jsqImage = [avatarFactory avatarImageWithUserInitials:@"JSQ" backgroundColor:[UIColor colorWithWhite:0.85f alpha:1.0f] textColor:[UIColor colorWithWhite:0.60f alpha:1.0f] font:[UIFont systemFontOfSize:14.0f]]; JSQMessagesAvatarImage *cookImage = [avatarFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_cook"]]; JSQMessagesAvatarImage *jobsImage = [avatarFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_jobs"]]; JSQMessagesAvatarImage *wozImage = [avatarFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_woz"]]; self.avatars = @{ kJSQDemoAvatarIdSquires : jsqImage, kJSQDemoAvatarIdCook : cookImage, kJSQDemoAvatarIdJobs : jobsImage, kJSQDemoAvatarIdWoz : wozImage }; self.users = @{ kJSQDemoAvatarIdJobs : kJSQDemoAvatarDisplayNameJobs, kJSQDemoAvatarIdCook : kJSQDemoAvatarDisplayNameCook, kJSQDemoAvatarIdWoz : kJSQDemoAvatarDisplayNameWoz, kJSQDemoAvatarIdSquires : kJSQDemoAvatarDisplayNameSquires }; /** * Create message bubble images objects. * * Be sure to create your bubble images one time and reuse them for good performance. * */ JSQMessagesBubbleImageFactory *bubbleFactory = [[JSQMessagesBubbleImageFactory alloc] init]; self.outgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleLightGrayColor]]; self.incomingBubbleImageData = [bubbleFactory incomingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleGreenColor]]; } return self; } - (void)loadFakeMessages { /** * Load some fake messages for demo. * * You should have a mutable array or orderedSet, or something. */ self.messages = [[NSMutableArray alloc] initWithObjects: [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdSquires senderDisplayName:kJSQDemoAvatarDisplayNameSquires date:[NSDate distantPast] text:NSLocalizedString(@"Welcome to JSQMessages: A messaging UI framework for iOS.", nil)], [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdWoz senderDisplayName:kJSQDemoAvatarDisplayNameWoz date:[NSDate distantPast] text:NSLocalizedString(@"It is simple, elegant, and easy to use. There are super sweet default settings, but you can customize like crazy.", nil)], [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdSquires senderDisplayName:kJSQDemoAvatarDisplayNameSquires date:[NSDate distantPast] text:NSLocalizedString(@"It even has data detectors. You can call me tonight. My cell number is 123-456-7890. My website is www.hexedbits.com.", nil)], [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdJobs senderDisplayName:kJSQDemoAvatarDisplayNameJobs date:[NSDate date] text:NSLocalizedString(@"JSQMessagesViewController is nearly an exact replica of the iOS Messages App. And perhaps, better.", nil)], [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdCook senderDisplayName:kJSQDemoAvatarDisplayNameCook date:[NSDate date] text:NSLocalizedString(@"It is unit-tested, free, open-source, and documented.", nil)], [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdSquires senderDisplayName:kJSQDemoAvatarDisplayNameSquires date:[NSDate date] text:NSLocalizedString(@"Now with media messages!", nil)], nil]; [self addPhotoMediaMessage]; [self addAudioMediaMessage]; /** * Setting to load extra messages for testing/demo */ if ([NSUserDefaults extraMessagesSetting]) { NSArray *copyOfMessages = [self.messages copy]; for (NSUInteger i = 0; i < 4; i++) { [self.messages addObjectsFromArray:copyOfMessages]; } } /** * Setting to load REALLY long message for testing/demo * You should see "END" twice */ if ([NSUserDefaults longMessageSetting]) { JSQMessage *reallyLongMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires displayName:kJSQDemoAvatarDisplayNameSquires text:@"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? END Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? END"]; [self.messages addObject:reallyLongMessage]; } } - (void)addAudioMediaMessage { NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"]; NSData * audioData = [NSData dataWithContentsOfFile:sample]; JSQAudioMediaItem *audioItem = [[JSQAudioMediaItem alloc] initWithData:audioData]; JSQMessage *audioMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires displayName:kJSQDemoAvatarDisplayNameSquires media:audioItem]; [self.messages addObject:audioMessage]; } - (void)addPhotoMediaMessage { JSQPhotoMediaItem *photoItem = [[JSQPhotoMediaItem alloc] initWithImage:[UIImage imageNamed:@"goldengate"]]; JSQMessage *photoMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires displayName:kJSQDemoAvatarDisplayNameSquires media:photoItem]; [self.messages addObject:photoMessage]; } - (void)addLocationMediaMessageCompletion:(JSQLocationMediaItemCompletionBlock)completion { CLLocation *ferryBuildingInSF = [[CLLocation alloc] initWithLatitude:37.795313 longitude:-122.393757]; JSQLocationMediaItem *locationItem = [[JSQLocationMediaItem alloc] init]; [locationItem setLocation:ferryBuildingInSF withCompletionHandler:completion]; JSQMessage *locationMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires displayName:kJSQDemoAvatarDisplayNameSquires media:locationItem]; [self.messages addObject:locationMessage]; } - (void)addVideoMediaMessage { // don't have a real video, just pretending NSURL *videoURL = [NSURL URLWithString:@"file://"]; JSQVideoMediaItem *videoItem = [[JSQVideoMediaItem alloc] initWithFileURL:videoURL isReadyToPlay:YES]; JSQMessage *videoMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires displayName:kJSQDemoAvatarDisplayNameSquires media:videoItem]; [self.messages addObject:videoMessage]; } - (void)addVideoMediaMessageWithThumbnail { // don't have a real video, just pretending NSURL *videoURL = [NSURL URLWithString:@"file://"]; JSQVideoMediaItem *videoItem = [[JSQVideoMediaItem alloc] initWithFileURL:videoURL isReadyToPlay:YES thumbnailImage:[UIImage imageNamed:@"goldengate"]]; JSQMessage *videoMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires displayName:kJSQDemoAvatarDisplayNameSquires media:videoItem]; [self.messages addObject:videoMessage]; } @end ================================================ FILE: JSQMessagesDemo/DemoSettingsViewController.h ================================================ // // 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 #import /** * This is for demo/testing purposes only. * * This is a terrible idea for a real app. */ @interface DemoSettingsViewController : UITableViewController @property (weak, nonatomic) IBOutlet UISwitch *extraMessagesSwitch; @property (weak, nonatomic) IBOutlet UISwitch *longMessageSwitch; @property (weak, nonatomic) IBOutlet UISwitch *emptySwitch; @property (weak, nonatomic) IBOutlet UISwitch *incomingAvatarsSwitch; @property (weak, nonatomic) IBOutlet UISwitch *outgoingAvatarsSwitch; @property (weak, nonatomic) IBOutlet UISwitch *springySwitch; @property (weak, nonatomic) IBOutlet UISwitch *accessoryButtonSwitch; @end ================================================ FILE: JSQMessagesDemo/DemoSettingsViewController.m ================================================ // // 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 "DemoSettingsViewController.h" #import "NSUserDefaults+DemoSettings.h" /** * This is for demo/testing purposes only. * * This is a terrible idea for a real app. */ @implementation DemoSettingsViewController - (void)viewDidLoad { [super viewDidLoad]; self.extraMessagesSwitch.on = [NSUserDefaults extraMessagesSetting]; self.longMessageSwitch.on = [NSUserDefaults longMessageSetting]; self.emptySwitch.on = [NSUserDefaults emptyMessagesSetting]; self.accessoryButtonSwitch.on = [NSUserDefaults accessoryButtonForMediaMessages]; self.incomingAvatarsSwitch.on = [NSUserDefaults incomingAvatarSetting]; self.outgoingAvatarsSwitch.on = [NSUserDefaults outgoingAvatarSetting]; self.springySwitch.on = [NSUserDefaults springinessSetting]; } - (IBAction)didTapSwitch:(UISwitch *)sender { if (sender == self.extraMessagesSwitch) { [NSUserDefaults saveExtraMessagesSetting:sender.on]; } else if (sender == self.longMessageSwitch) { [NSUserDefaults saveLongMessageSetting:sender.on]; } else if (sender == self.emptySwitch) { [NSUserDefaults saveEmptyMessagesSetting:sender.on]; } else if (sender == self.accessoryButtonSwitch) { [NSUserDefaults saveAccessoryButtonForMediaMessages:sender.on]; } else if (sender == self.incomingAvatarsSwitch) { [NSUserDefaults saveIncomingAvatarSetting:sender.on]; } else if (sender == self.outgoingAvatarsSwitch) { [NSUserDefaults saveOutgoingAvatarSetting:sender.on]; } else if (sender == self.springySwitch) { [NSUserDefaults saveSpringinessSetting:sender.on]; } [[NSUserDefaults standardUserDefaults] synchronize]; } @end ================================================ FILE: JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-Small@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-87.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-Spotlight-iOS7@2x-1.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-120-1.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-120.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-180.png", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-Small.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-Small@2x-1.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-Spotlight-iOS7.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-Spotlight-iOS7@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-76.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "icon167.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JSQMessagesDemo/Images.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "demo_avatar_cook.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "demo_avatar_cook@2x.png" }, { "idiom" : "universal", "scale" : "3x", "filename" : "demo_avatar_cook@3x.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "demo_avatar_jobs.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "demo_avatar_jobs@2x.png" }, { "idiom" : "universal", "scale" : "3x", "filename" : "demo_avatar_jobs@3x.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "demo_avatar_woz.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "demo_avatar_woz@2x.png" }, { "idiom" : "universal", "scale" : "3x", "filename" : "demo_avatar_woz@3x.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "736h", "filename" : "jsq_messages_splash_55inch.png", "minimum-system-version" : "8.0", "orientation" : "portrait", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "736h", "filename" : "jsq_messages_splash_55inch_landscape.png", "minimum-system-version" : "8.0", "orientation" : "landscape", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "667h", "filename" : "jsq_messages_splash_47inch.png", "minimum-system-version" : "8.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "splash_iphone35inch.png", "scale" : "2x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "retina4", "filename" : "slpash_iphone4inch.png", "minimum-system-version" : "7.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "jsq_messages_splash_ipad.png", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "jsq_messages_splash_ipad~landscape.png", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "jsq_messages_splash_ipad@2x.png", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "jsq_messages_splash_ipad@2x~landscape.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JSQMessagesDemo/Images.xcassets/goldengate.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "goldengate.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "goldengate@2x.png" }, { "idiom" : "universal", "scale" : "3x", "filename" : "goldengate@3x.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JSQMessagesDemo/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 7.3.4 CFBundleSignature ???? CFBundleVersion 7.3.4 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: JSQMessagesDemo/NSUserDefaults+DemoSettings.h ================================================ // // 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 @interface NSUserDefaults (DemoSettings) + (void)saveExtraMessagesSetting:(BOOL)value; + (BOOL)extraMessagesSetting; + (void)saveLongMessageSetting:(BOOL)value; + (BOOL)longMessageSetting; + (void)saveEmptyMessagesSetting:(BOOL)value; + (BOOL)emptyMessagesSetting; + (void)saveSpringinessSetting:(BOOL)value; + (BOOL)springinessSetting; + (void)saveOutgoingAvatarSetting:(BOOL)value; + (BOOL)outgoingAvatarSetting; + (void)saveIncomingAvatarSetting:(BOOL)value; + (BOOL)incomingAvatarSetting; + (void)saveAccessoryButtonForMediaMessages:(BOOL)value; + (BOOL)accessoryButtonForMediaMessages; @end ================================================ FILE: JSQMessagesDemo/NSUserDefaults+DemoSettings.m ================================================ // // 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 "NSUserDefaults+DemoSettings.h" static NSString * const kSettingExtraMessages = @"kSettingExtraMessages"; static NSString * const kSettingLongMessage = @"kSettingLongMessage"; static NSString * const kSettingEmptyMessages = @"kSettingEmptyMessages"; static NSString * const kSettingSpringiness = @"kSettingSpringiness"; static NSString * const kSettingIncomingAvatar = @"kSettingIncomingAvatar"; static NSString * const kSettingOutgoingAvatar = @"kSettingOutgoingAvatar"; static NSString * const kSettingAccessoryButtonForMedia = @"kSettingAccessoryButtonForMedia"; @implementation NSUserDefaults (DemoSettings) + (void)saveExtraMessagesSetting:(BOOL)value { [[NSUserDefaults standardUserDefaults] setBool:value forKey:kSettingExtraMessages]; } + (BOOL)extraMessagesSetting { return [[NSUserDefaults standardUserDefaults] boolForKey:kSettingExtraMessages]; } + (void)saveLongMessageSetting:(BOOL)value { [[NSUserDefaults standardUserDefaults] setBool:value forKey:kSettingLongMessage]; } + (BOOL)longMessageSetting { return [[NSUserDefaults standardUserDefaults] boolForKey:kSettingLongMessage]; } + (void)saveEmptyMessagesSetting:(BOOL)value { [[NSUserDefaults standardUserDefaults] setBool:value forKey:kSettingEmptyMessages]; } + (BOOL)emptyMessagesSetting { return [[NSUserDefaults standardUserDefaults] boolForKey:kSettingEmptyMessages]; } + (void)saveSpringinessSetting:(BOOL)value { [[NSUserDefaults standardUserDefaults] setBool:value forKey:kSettingSpringiness]; } + (BOOL)springinessSetting { return [[NSUserDefaults standardUserDefaults] boolForKey:kSettingSpringiness]; } + (void)saveOutgoingAvatarSetting:(BOOL)value { [[NSUserDefaults standardUserDefaults] setBool:value forKey:kSettingOutgoingAvatar]; } + (BOOL)outgoingAvatarSetting { return [[NSUserDefaults standardUserDefaults] boolForKey:kSettingOutgoingAvatar]; } + (void)saveIncomingAvatarSetting:(BOOL)value { [[NSUserDefaults standardUserDefaults] setBool:value forKey:kSettingIncomingAvatar]; } + (BOOL)incomingAvatarSetting { return [[NSUserDefaults standardUserDefaults] boolForKey:kSettingIncomingAvatar]; } + (BOOL)accessoryButtonForMediaMessages { return [[NSUserDefaults standardUserDefaults] boolForKey:kSettingAccessoryButtonForMedia]; } + (void)saveAccessoryButtonForMediaMessages:(BOOL)value { [[NSUserDefaults standardUserDefaults] setBool:value forKey:kSettingAccessoryButtonForMedia]; } @end ================================================ FILE: JSQMessagesDemo/TableViewController.h ================================================ // // 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 #import #import "DemoMessagesViewController.h" @interface TableViewController : UITableViewController - (IBAction)unwindSegue:(UIStoryboardSegue *)sender; @end ================================================ FILE: JSQMessagesDemo/TableViewController.m ================================================ // // 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 "TableViewController.h" @implementation TableViewController #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; self.title = @"JSQMessagesViewController"; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES]; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 4; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { switch (section) { case 0: return 2; case 1: return 2; case 2: return 1; case 3: return 1; default: return 0; } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"CellIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } if (indexPath.section == 0) { switch (indexPath.row) { case 0: cell.textLabel.text = @"Push via storyboard"; break; case 1: cell.textLabel.text = @"Push programmatically"; break; } } else if (indexPath.section == 1) { switch (indexPath.row) { case 0: cell.textLabel.text = @"Modal via storyboard"; break; case 1: cell.textLabel.text = @"Modal programmatically"; break; } } else if (indexPath.section == 2) { switch (indexPath.row) { case 0: cell.textLabel.text = @"Settings"; break; } } else if (indexPath.section == 3) { switch (indexPath.row) { case 0: cell.textLabel.text = @"Push view 2 levels"; break; } } return cell; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { switch (section) { case 0: return @"Presentation"; case 2: return @"Demo options"; case 3: return @"Other testing"; default: return nil; } } - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { return (section == 2) ? @"Copyright © 2015\nJesse Squires\nMIT License" : nil; } #pragma mark - Table view delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { switch (indexPath.row) { case 0: [self performSegueWithIdentifier:@"seguePushDemoVC" sender:self]; break; case 1: { DemoMessagesViewController *vc = [DemoMessagesViewController messagesViewController]; [self.navigationController pushViewController:vc animated:YES]; } break; } } else if (indexPath.section == 1) { switch (indexPath.row) { case 0: [self performSegueWithIdentifier:@"segueModalDemoVC" sender:self]; break; case 1: { DemoMessagesViewController *vc = [DemoMessagesViewController messagesViewController]; vc.delegateModal = self; UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc]; [self presentViewController:nc animated:YES completion:nil]; } break; } } else if (indexPath.section == 2) { switch (indexPath.row) { case 0: [self performSegueWithIdentifier:@"SegueToSettings" sender:self]; break; } } else if (indexPath.section == 3) { switch (indexPath.row) { case 0: { UIViewController *blank = [[UIViewController alloc] initWithNibName:nil bundle:nil]; blank.title = @"Blank"; blank.view.backgroundColor = [UIColor lightGrayColor]; [self.navigationController pushViewController:blank animated:NO]; DemoMessagesViewController *vc = [DemoMessagesViewController messagesViewController]; [self.navigationController pushViewController:vc animated:YES]; } } } } #pragma mark - Segues - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"segueModalDemoVC"]) { UINavigationController *nc = segue.destinationViewController; DemoMessagesViewController *vc = (DemoMessagesViewController *)nc.topViewController; vc.delegateModal = self; } } - (IBAction)unwindSegue:(UIStoryboardSegue *)sender { } #pragma mark - Demo delegate - (void)didDismissJSQDemoViewController:(DemoMessagesViewController *)vc { [self dismissViewControllerAnimated:YES completion:nil]; } @end ================================================ FILE: JSQMessagesDemo/he.lproj/Localizable.strings ================================================ // // 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 // "Media messages" = "הודעות מדיה"; "Cancel" = "ביטול"; "Send photo" = "שלח תמונה"; "Send location" = "שלח מיקום"; "Send video" = "שלח וידאו"; "Custom Action" = "פעולה מותאמת אישית"; "OK" = "אוקי"; "Welcome to JSQMessages: A messaging UI framework for iOS." = "ברוכים הבאים ל JSQMessages ספריית UI צא׳ט עבור מכשירי iOS"; "It is simple, elegant, and easy to use. There are super sweet default settings, but you can customize like crazy." = "זה פשוט, אלגנטי, וקל לשימוש. ישנן הגדרות ברירת מחדל מאוד סולידיות ,איך ניתן להגדיר אותן אישית כמו משוגע."; "It even has data detectors. You can call me tonight. My cell number is 123-456-7890. My website is www.hexedbits.com." = "יש לו אפילו זיהוי טקסט, אתה יכול להתקשר אלי. מספר הטלפון שלי הוא: 123-456-7890. ואתר האינטרנט שלי הוא: www.hexedbits.com"; "JSQMessagesViewController is nearly an exact replica of the iOS Messages App. And perhaps, better." = "הספריה JSQMessagesViewController היא כמעט זהה לאפליקציית ההודעות של מכשיר ה iOS. ואולי טובה יותר."; "It is unit-tested, free, open-source, and documented." = "ישנן בדיקות יחידה, זה חינם, זה קוד-פתוח ויש אחלה דקומנטציה"; "Now with media messages!" = "וישנה גם תמיכה בהודעות מדיה"; ================================================ FILE: JSQMessagesDemo/he.lproj/Main.strings ================================================ /* Class = "UILabel"; text = "Title"; ObjectID = "2qz-Z2-GmT"; */ "2qz-Z2-GmT.text" = "כותרת"; /* Class = "UILabel"; text = "Springy bubbles"; ObjectID = "3d2-fZ-dx9"; */ "3d2-fZ-dx9.text" = "Springy bubbles"; /* Class = "UILabel"; text = "Outgoing avatars"; ObjectID = "9Rr-S8-Uae"; */ "9Rr-S8-Uae.text" = "אווטאר בהודעות יוצאות"; /* Class = "UILabel"; text = "Accessory button for media messages"; ObjectID = "Cae-FY-b5Q"; */ "Cae-FY-b5Q.text" = "Accessory button for media messages"; /* Class = "UILabel"; text = "Empty view, no messages"; ObjectID = "DoU-SU-Nek"; */ "DoU-SU-Nek.text" = "מסך רייק ללא הודעות"; /* Class = "UILabel"; text = "Incoming avatars"; ObjectID = "RUq-Pa-3nx"; */ "RUq-Pa-3nx.text" = "אווטאר בהודעות נכנסות"; /* Class = "UILabel"; text = "Load really long message"; ObjectID = "YV3-GH-Yul"; */ "YV3-GH-Yul.text" = "טען הודעה ארוכה מאוד"; /* Class = "UILabel"; text = "Load extra messages"; ObjectID = "bSS-CD-nfD"; */ "bSS-CD-nfD.text" = "טען הודעות נוספות"; /* Class = "UINavigationItem"; title = "Settings"; ObjectID = "hrw-Dp-Tor"; */ "hrw-Dp-Tor.title" = "הגדרות"; /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "irr-Pn-9x5"; */ "irr-Pn-9x5.title" = "Root View Controller"; /* Class = "UITableViewSection"; headerTitle = "Avatars"; ObjectID = "ns0-OO-PGu"; */ "ns0-OO-PGu.headerTitle" = "אווטאר"; /* Class = "UITableViewSection"; footerTitle = "NOTE: This feature is experimental"; ObjectID = "o5m-OT-1Iw"; */ "o5m-OT-1Iw.footerTitle" = "שים לב: פיצ'ר זו היינו ניסיוני"; /* Class = "UITableViewSection"; headerTitle = "Dynamic Behaviors"; ObjectID = "o5m-OT-1Iw"; */ "o5m-OT-1Iw.headerTitle" = "Dynamic Behaviors"; /* Class = "UITableViewSection"; headerTitle = "Messages"; ObjectID = "ygb-Dp-o4r"; */ "ygb-Dp-o4r.headerTitle" = "הודעות"; ================================================ FILE: JSQMessagesDemo/main.m ================================================ // // 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: JSQMessagesTests/CategoryTests/JSQMessagesNSBundleTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #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 ================================================ FILE: JSQMessagesTests/CategoryTests/JSQMessagesNSStringTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "NSString+JSQMessages.h" @interface JSQMessagesNSStringTests : XCTestCase @end @implementation JSQMessagesNSStringTests - (void)testTrimingStringWhitespace { // GIVEN: a string of text NSString *loremIpsum = @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; // WHEN: the text is wrapped in white space NSString *string1 = [NSString stringWithFormat:@" %@ ", loremIpsum]; NSString *string2 = [NSString stringWithFormat:@" %@", loremIpsum]; NSString *string3 = [NSString stringWithFormat:@"%@ ", loremIpsum]; // THEN: we can successfully trim extra white space XCTAssertEqualObjects(loremIpsum, [string1 jsq_stringByTrimingWhitespace], @"Strings should be equal after trimming whitespace"); XCTAssertEqualObjects(loremIpsum, [string2 jsq_stringByTrimingWhitespace], @"Strings should be equal after trimming whitespace"); XCTAssertEqualObjects(loremIpsum, [string3 jsq_stringByTrimingWhitespace], @"Strings should be equal after trimming whitespace"); } @end ================================================ FILE: JSQMessagesTests/CategoryTests/JSQMessagesUIColorTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "UIColor+JSQMessages.h" @interface JSQMessagesUIColorTests : XCTestCase @end @implementation JSQMessagesUIColorTests - (void)testDarkeningColors { // GIVEN: a color and darkening value CGFloat r = 0.89f, g = 0.34f, b = 0.67f, a = 1.0f; CGFloat darkeningValue = 0.12f; UIColor *color = [UIColor colorWithRed:r green:g blue:b alpha:a]; // WHEN: we darken that color UIColor *darkColor = [color jsq_colorByDarkeningColorWithValue:darkeningValue]; // THEN: each RGB value is changed accordingly CGFloat dr, dg, db, da; [darkColor getRed:&dr green:&dg blue:&db alpha:&da]; XCTAssertEqual(dr, r - darkeningValue, @"Red values should be equal"); XCTAssertEqual(dg, g - darkeningValue, @"Green values should be equal"); XCTAssertEqual(db, b - darkeningValue, @"Blue values should be equal"); XCTAssertEqual(da, a, @"Alpha values should be equal"); } - (void)testDarkeningColorsFloorToZero { // GIVEN: a color and darkening value CGFloat r = 0.89f, g = 0.24f, b = 0.67f, a = 1.0f; CGFloat darkeningValue = 0.5f; UIColor *color = [UIColor colorWithRed:r green:g blue:b alpha:a]; // WHEN: we dark that color, such that some RGB values will be negative UIColor *darkColor = [color jsq_colorByDarkeningColorWithValue:darkeningValue]; // THEN: the RGB values are floored to zero instead of being negative CGFloat dr, dg, db, da; [darkColor getRed:&dr green:&dg blue:&db alpha:&da]; XCTAssertEqual(dr, r - darkeningValue, @"Red values should be equal"); XCTAssertEqual(dg, 0.0f, @"Green values should be floored to zero"); XCTAssertEqual(db, b - darkeningValue, @"Blue values should be equal"); XCTAssertEqual(da, a, @"Alpha values should be equal"); } @end ================================================ FILE: JSQMessagesTests/CategoryTests/JSQMessagesUIImageTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "UIImage+JSQMessages.h" @interface JSQMessagesUIImageTests : XCTestCase @end @implementation JSQMessagesUIImageTests - (void)testImageMasking { // GIVEN: an image UIImage *img = [UIImage jsq_bubbleCompactImage]; XCTAssertNotNil(img, @"Image should not be nil"); // WHEN: we mask that image UIImage *imgMasked = [img jsq_imageMaskedWithColor:[UIColor whiteColor]]; XCTAssertNotNil(imgMasked, @"Image should not be nil"); // THEN: masking should succeed, and the new image should have the same properties XCTAssertTrue(CGSizeEqualToSize(img.size, imgMasked.size), @"Image sizes should be equal"); XCTAssertEqual(img.scale, imgMasked.scale, @"Image scales should be equal"); } - (void)testImageAssets { // GIVEN: our image assets // WHEN: we create a new UIImage object // THEN: the image is created successfully XCTAssertNotNil([UIImage jsq_bubbleRegularImage]); XCTAssertNotNil([UIImage jsq_bubbleRegularTaillessImage]); XCTAssertNotNil([UIImage jsq_bubbleRegularStrokedImage]); XCTAssertNotNil([UIImage jsq_bubbleRegularStrokedTaillessImage]); XCTAssertNotNil([UIImage jsq_bubbleCompactImage]); XCTAssertNotNil([UIImage jsq_bubbleCompactTaillessImage]); XCTAssertNotNil([UIImage jsq_defaultAccessoryImage]); XCTAssertNotNil([UIImage jsq_defaultTypingIndicatorImage]); XCTAssertNotNil([UIImage jsq_defaultPlayImage]); XCTAssertNotNil([UIImage jsq_shareActionImage]); } @end ================================================ FILE: JSQMessagesTests/CategoryTests/JSQMessagesUIViewTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "UIView+JSQMessages.h" @interface JSQMessagesUIViewTests : XCTestCase @end @implementation JSQMessagesUIViewTests - (void)testViewAutoLayoutPinEdges { // GIVEN: a superview and subview UIView *superview = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)]; [superview setTranslatesAutoresizingMaskIntoConstraints:NO]; UIView *subview = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 25.0f, 25.0f)]; [subview setTranslatesAutoresizingMaskIntoConstraints:NO]; // WHEN: we add the subview to the superview [superview addSubview:subview]; // WHEN: we pin the edges of the subview to the superview XCTAssertNoThrow([superview jsq_pinAllEdgesOfSubview:subview], @"Pinning edges of subview to superview should not throw"); [superview setNeedsUpdateConstraints]; [superview layoutIfNeeded]; // THEN: add the layout constraints and laying out the views succeeds XCTAssertEqual([[superview constraints] count], 4U, @"Superview should have 4 constraints"); XCTAssertEqual([[subview constraints] count], 0U, @"Subview should have 0 constraints"); for (NSLayoutConstraint *eachConstraint in [superview constraints]) { XCTAssertEqualObjects(eachConstraint.firstItem, superview, @"Constraint first item should be equal to superview"); XCTAssertEqualObjects(eachConstraint.secondItem, subview, @"Constraint second item should be equal to subview"); XCTAssertEqual(eachConstraint.relation, NSLayoutRelationEqual, @"Constraint relation should be NSLayoutRelationEqual"); XCTAssertEqual(eachConstraint.multiplier, 1.0f, @"Constraint multiplier should be 1.0"); XCTAssertEqual(eachConstraint.constant, 0.0f, @"Constraint constant should be 0.0"); } } @end ================================================ FILE: JSQMessagesTests/ControllerTests/JSQMessagesViewControllerTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesViewController.h" #import "DemoMessagesViewController.h" @interface JSQMessagesViewController () - (void)jsq_configureMessagesViewController; @end @interface JSQMessagesViewControllerTests : XCTestCase @end @implementation JSQMessagesViewControllerTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testJSQMessagesViewControllerInit { UINib *nib = [JSQMessagesViewController nib]; XCTAssertNotNil(nib, @"Nib should not be nil"); JSQMessagesViewController *vc = [JSQMessagesViewController messagesViewController]; [vc beginAppearanceTransition:YES animated:NO]; [vc endAppearanceTransition]; XCTAssertNotNil(vc, @"View controller should not be nil"); 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"); XCTAssertEqual(vc.automaticallyAdjustsScrollViewInsets, YES, @"Property should be equal to default value"); XCTAssertEqualObjects(vc.incomingCellIdentifier, [JSQMessagesCollectionViewCellIncoming cellReuseIdentifier], @"Property should be equal to default value"); XCTAssertEqualObjects(vc.outgoingCellIdentifier, [JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier], @"Property should be equal to default value"); XCTAssertEqual(vc.showTypingIndicator, NO, @"Property should be equal to default value"); XCTAssertEqual(vc.showLoadEarlierMessagesHeader, NO, @"Property should be equal to default value"); } - (void)testJSQMessagesViewControllerSubclassInitProgramatically { DemoMessagesViewController *demoVC = [DemoMessagesViewController messagesViewController]; [demoVC beginAppearanceTransition:YES animated:NO]; [demoVC endAppearanceTransition]; XCTAssertNotNil(demoVC, @"View controller should not be nil"); XCTAssertTrue([demoVC isKindOfClass:[DemoMessagesViewController class]], @"View controller should be kind of class: %@", [DemoMessagesViewController class]); XCTAssertNotNil(demoVC.view, @"View should not be nil"); XCTAssertNotNil(demoVC.collectionView, @"Collection view should not be nil"); XCTAssertNotNil(demoVC.inputToolbar, @"Input toolbar should not be nil"); } - (void)testJSQMessagesViewControllerSubclassInitStoryboards { UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; XCTAssertNotNil(mainSB, @"Storyboard should not be nil"); DemoMessagesViewController *demoVC = [mainSB instantiateViewControllerWithIdentifier:@"DemoVC"]; [demoVC beginAppearanceTransition:YES animated:NO]; [demoVC endAppearanceTransition]; XCTAssertNotNil(demoVC, @"View controller should not be nil"); XCTAssertTrue([demoVC isKindOfClass:[DemoMessagesViewController class]], @"View controller should be kind of class: %@", [DemoMessagesViewController class]); XCTAssertNotNil(demoVC.view, @"View should not be nil"); XCTAssertNotNil(demoVC.collectionView, @"Collection view should not be nil"); XCTAssertNotNil(demoVC.inputToolbar, @"Input toolbar should not be nil"); } @end ================================================ FILE: JSQMessagesTests/FactoryTests/JSQMessagesAvatarImageFactoryTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesAvatarImageFactory.h" @interface JSQMessagesAvatarImageFactoryTests : XCTestCase @property (strong, nonatomic) JSQMessagesAvatarImageFactory *factory; @property (assign, nonatomic) NSUInteger avatarDiameter; @end @implementation JSQMessagesAvatarImageFactoryTests - (void)setUp { [super setUp]; self.avatarDiameter = 50.0f; self.factory = [[JSQMessagesAvatarImageFactory alloc] initWithDiameter:self.avatarDiameter]; } - (void)tearDown { [super tearDown]; self.factory = nil; } - (CGSize)avatarSize { return CGSizeMake(self.avatarDiameter, self.avatarDiameter); } - (void)testAvatarImage { UIImage *image = [UIImage imageNamed:@"demo_avatar_jobs"]; XCTAssertNotNil(image, @"Image should not be nil"); JSQMessagesAvatarImage *avatar = [self.factory avatarImageWithPlaceholder:image]; XCTAssertNotNil(avatar, @"Avatar should not be nil"); XCTAssertTrue(CGSizeEqualToSize(avatar.avatarPlaceholderImage.size, [self avatarSize]), @"Avatar size should be equal to diameter"); XCTAssertEqual(avatar.avatarPlaceholderImage.scale, [UIScreen mainScreen].scale, @"Avatar scale should be equal to screen scale"); avatar.avatarImage = [self.factory circularAvatarImage:image]; XCTAssertTrue(CGSizeEqualToSize(avatar.avatarImage.size, [self avatarSize]), @"Avatar size should be equal to diameter"); XCTAssertEqual(avatar.avatarImage.scale, [UIScreen mainScreen].scale, @"Avatar scale should be equal to screen scale"); avatar.avatarHighlightedImage = [self.factory circularAvatarHighlightedImage:image]; XCTAssertTrue(CGSizeEqualToSize(avatar.avatarHighlightedImage.size, [self avatarSize]), @"Avatar size should be equal to diameter"); XCTAssertEqual(avatar.avatarHighlightedImage.scale, [UIScreen mainScreen].scale, @"Avatar scale should be equal to screen scale"); } - (void)testAvatarInitialsImage { JSQMessagesAvatarImage *avatar = [self.factory avatarImageWithUserInitials:@"JSQ" backgroundColor:[UIColor lightGrayColor] textColor:[UIColor darkGrayColor] font:[UIFont systemFontOfSize:13.0f]]; XCTAssertNotNil(avatar, @"Avatar should not be nil"); XCTAssertTrue(CGSizeEqualToSize(avatar.avatarImage.size, [self avatarSize]), @"Avatar size should be equal to diameter"); XCTAssertEqual(avatar.avatarImage.scale, [UIScreen mainScreen].scale, @"Avatar scale should be equal to screen scale"); XCTAssertTrue(CGSizeEqualToSize(avatar.avatarHighlightedImage.size, [self avatarSize]), @"Avatar size should be equal to diameter"); XCTAssertEqual(avatar.avatarHighlightedImage.scale, [UIScreen mainScreen].scale, @"Avatar scale should be equal to screen scale"); XCTAssertTrue(CGSizeEqualToSize(avatar.avatarPlaceholderImage.size, [self avatarSize]), @"Avatar size should be equal to diameter"); XCTAssertEqual(avatar.avatarPlaceholderImage.scale, [UIScreen mainScreen].scale, @"Avatar scale should be equal to screen scale"); } @end ================================================ FILE: JSQMessagesTests/FactoryTests/JSQMessagesBubbleImageFactoryTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesBubbleImageFactory.h" #import "UIImage+JSQMessages.h" @interface JSQMessagesBubbleImageFactoryTests : XCTestCase @property (strong, nonatomic) JSQMessagesBubbleImageFactory *factory; @end @implementation JSQMessagesBubbleImageFactoryTests - (void)setUp { [super setUp]; self.factory = [[JSQMessagesBubbleImageFactory alloc] init]; } - (void)tearDown { self.factory = nil; [super tearDown]; } - (void)testOutgoingMessageBubbleImageViewRigthtToLeftDirectionText { UIImage *bubble = [UIImage jsq_bubbleCompactImage]; XCTAssertNotNil(bubble, @"Bubble image should not be nil"); if ([UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { bubble = [UIImage imageWithCGImage:[bubble CGImage] scale:[bubble scale] orientation: UIImageOrientationUpMirrored]; } CGPoint center = CGPointMake(bubble.size.width / 2.0f, bubble.size.height / 2.0f); UIEdgeInsets capInsets = UIEdgeInsetsMake(center.y, center.x, center.y, center.x); JSQMessagesBubbleImage *bubbleImage = [self.factory outgoingMessagesBubbleImageWithColor:[UIColor lightGrayColor]]; XCTAssertNotNil(bubbleImage, @"Bubble image should not be nil"); XCTAssertNotNil(bubbleImage.messageBubbleImage, "Image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleImage.scale, bubble.scale, @"Image scale should equal bubble image scale"); XCTAssertEqual(bubbleImage.messageBubbleImage.imageOrientation, bubble.imageOrientation, @"Image orientation should equal bubble image orientation"); XCTAssertTrue(bubbleImage.messageBubbleImage.resizingMode == UIImageResizingModeStretch, @"Image should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleImage.capInsets, capInsets), @"Image capInsets should be equal to capInsets"); XCTAssertNotNil(bubbleImage.messageBubbleHighlightedImage, @"Highlighted image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.scale, bubble.scale, @"HighlightedImage scale should equal bubble image scale"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.imageOrientation, bubble.imageOrientation, @"HighlightedImage orientation should equal bubble image orientation"); XCTAssertTrue(bubbleImage.messageBubbleHighlightedImage.resizingMode == UIImageResizingModeStretch, @"HighlightedImage should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleHighlightedImage.capInsets, capInsets), @"HighlightedImage capInsets should be equal to capInsets"); } - (void)testIncomingMessageBubbleImageView { UIImage *bubble = [UIImage jsq_bubbleCompactImage]; XCTAssertNotNil(bubble, @"Bubble image should not be nil"); if ([UIApplication sharedApplication].userInterfaceLayoutDirection != UIUserInterfaceLayoutDirectionRightToLeft) { bubble = [UIImage imageWithCGImage:[bubble CGImage] scale:[bubble scale] orientation: UIImageOrientationUpMirrored]; } CGPoint center = CGPointMake(bubble.size.width / 2.0f, bubble.size.height / 2.0f); UIEdgeInsets capInsets = UIEdgeInsetsMake(center.y, center.x, center.y, center.x); JSQMessagesBubbleImage *bubbleImage = [self.factory incomingMessagesBubbleImageWithColor:[UIColor lightGrayColor]]; XCTAssertNotNil(bubbleImage, @"Bubble image should not be nil"); XCTAssertNotNil(bubbleImage.messageBubbleImage, "Image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleImage.scale, bubble.scale, @"Image scale should equal bubble image scale"); XCTAssertEqual(bubbleImage.messageBubbleImage.imageOrientation, bubble.imageOrientation, @"Image orientation should be flipped"); XCTAssertTrue(bubbleImage.messageBubbleImage.resizingMode == UIImageResizingModeStretch, @"Image should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleImage.capInsets, capInsets), @"Image capInsets should be equal to capInsets"); XCTAssertNotNil(bubbleImage.messageBubbleHighlightedImage, @"Highlighted image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.scale, bubble.scale, @"HighlightedImage scale should equal bubble image scale"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.imageOrientation, bubble.imageOrientation, @"Image orientation should be flipped"); XCTAssertTrue(bubbleImage.messageBubbleHighlightedImage.resizingMode == UIImageResizingModeStretch, @"HighlightedImage should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleHighlightedImage.capInsets, capInsets), @"HighlightedImage capInsets should be equal to capInsets"); } - (void)testCustomOutgoingMessageBubbleImageViewRigthtToLeftDirectionText { UIImage *bubble = [UIImage jsq_bubbleRegularStrokedTaillessImage]; XCTAssertNotNil(bubble, @"Bubble image should not be nil"); UIEdgeInsets capInsets = UIEdgeInsetsMake(1, 1, 1, 1); JSQMessagesBubbleImageFactory *factory = [[JSQMessagesBubbleImageFactory alloc] initWithBubbleImage:bubble capInsets:capInsets layoutDirection:UIUserInterfaceLayoutDirectionLeftToRight]; JSQMessagesBubbleImage *bubbleImage = [factory outgoingMessagesBubbleImageWithColor:[UIColor lightGrayColor]]; XCTAssertNotNil(bubbleImage, @"Bubble image should not be nil"); XCTAssertNotNil(bubbleImage.messageBubbleImage, "Image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleImage.scale, bubble.scale, @"Image scale should equal bubble image scale"); XCTAssertEqual(bubbleImage.messageBubbleImage.imageOrientation, bubble.imageOrientation, @"Image orientation should equal bubble image orientation"); XCTAssertTrue(bubbleImage.messageBubbleImage.resizingMode == UIImageResizingModeStretch, @"Image should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleImage.capInsets, capInsets), @"Image capInsets should be equal to capInsets"); XCTAssertNotNil(bubbleImage.messageBubbleHighlightedImage, @"Highlighted image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.scale, bubble.scale, @"HighlightedImage scale should equal bubble image scale"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.imageOrientation, bubble.imageOrientation, @"HighlightedImage orientation should equal bubble image orientation"); XCTAssertTrue(bubbleImage.messageBubbleHighlightedImage.resizingMode == UIImageResizingModeStretch, @"HighlightedImage should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleHighlightedImage.capInsets, capInsets), @"HighlightedImage capInsets should be equal to capInsets"); } - (void)testCustomOutgoingMessageBubbleImageViewWithLeftToRigthtDirectionText { UIImage *bubble = [UIImage jsq_bubbleRegularStrokedTaillessImage]; XCTAssertNotNil(bubble, @"Bubble image should not be nil"); UIEdgeInsets capInsets = UIEdgeInsetsMake(1, 1, 1, 1); JSQMessagesBubbleImageFactory *factory = [[JSQMessagesBubbleImageFactory alloc] initWithBubbleImage:bubble capInsets:capInsets layoutDirection:UIUserInterfaceLayoutDirectionRightToLeft]; JSQMessagesBubbleImage *bubbleImage = [factory outgoingMessagesBubbleImageWithColor:[UIColor lightGrayColor]]; XCTAssertNotNil(bubbleImage, @"Bubble image should not be nil"); XCTAssertNotNil(bubbleImage.messageBubbleImage, "Image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleImage.scale, bubble.scale, @"Image scale should equal bubble image scale"); XCTAssertNotEqual(bubbleImage.messageBubbleImage.imageOrientation, bubble.imageOrientation, @"Image orientation should not equal bubble image orientation"); XCTAssertTrue(bubbleImage.messageBubbleImage.resizingMode == UIImageResizingModeStretch, @"Image should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleImage.capInsets, capInsets), @"Image capInsets should be equal to capInsets"); XCTAssertNotNil(bubbleImage.messageBubbleHighlightedImage, @"Highlighted image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.scale, bubble.scale, @"HighlightedImage scale should equal bubble image scale"); XCTAssertTrue(bubbleImage.messageBubbleHighlightedImage.resizingMode == UIImageResizingModeStretch, @"HighlightedImage should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleHighlightedImage.capInsets, capInsets), @"HighlightedImage capInsets should be equal to capInsets"); } - (void)testCustomIncomingMessageBubbleImageView { UIImage *bubble = [UIImage jsq_bubbleRegularStrokedTaillessImage]; XCTAssertNotNil(bubble, @"Bubble image should not be nil"); UIEdgeInsets capInsets = UIEdgeInsetsMake(1, 1, 1, 1); JSQMessagesBubbleImageFactory *factory = [[JSQMessagesBubbleImageFactory alloc] initWithBubbleImage:bubble capInsets:capInsets layoutDirection:UIUserInterfaceLayoutDirectionLeftToRight]; JSQMessagesBubbleImage *bubbleImage = [factory incomingMessagesBubbleImageWithColor:[UIColor lightGrayColor]]; XCTAssertNotNil(bubbleImage, @"Bubble image should not be nil"); XCTAssertNotNil(bubbleImage.messageBubbleImage, "Image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleImage.scale, bubble.scale, @"Image scale should equal bubble image scale"); XCTAssertEqual(bubbleImage.messageBubbleImage.imageOrientation, UIImageOrientationUpMirrored, @"Image orientation should be flipped"); XCTAssertTrue(bubbleImage.messageBubbleImage.resizingMode == UIImageResizingModeStretch, @"Image should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleImage.capInsets, capInsets), @"Image capInsets should be equal to capInsets"); XCTAssertNotNil(bubbleImage.messageBubbleHighlightedImage, @"Highlighted image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.scale, bubble.scale, @"HighlightedImage scale should equal bubble image scale"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.imageOrientation, UIImageOrientationUpMirrored, @"Image orientation should be flipped"); XCTAssertTrue(bubbleImage.messageBubbleHighlightedImage.resizingMode == UIImageResizingModeStretch, @"HighlightedImage should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleHighlightedImage.capInsets, capInsets), @"HighlightedImage capInsets should be equal to capInsets"); } - (void)testCustomIncomingMessageBubbleImageViewWithLeftToRigthtDirectionText { UIImage *bubble = [UIImage jsq_bubbleRegularStrokedTaillessImage]; XCTAssertNotNil(bubble, @"Bubble image should not be nil"); UIEdgeInsets capInsets = UIEdgeInsetsMake(1, 1, 1, 1); JSQMessagesBubbleImageFactory *factory = [[JSQMessagesBubbleImageFactory alloc] initWithBubbleImage:bubble capInsets:capInsets layoutDirection:UIUserInterfaceLayoutDirectionRightToLeft]; JSQMessagesBubbleImage *bubbleImage = [factory incomingMessagesBubbleImageWithColor:[UIColor lightGrayColor]]; XCTAssertNotNil(bubbleImage, @"Bubble image should not be nil"); XCTAssertNotNil(bubbleImage.messageBubbleImage, "Image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleImage.scale, bubble.scale, @"Image scale should equal bubble image scale"); XCTAssertNotEqual(bubbleImage.messageBubbleImage.imageOrientation, UIImageOrientationUpMirrored, @"Image orientation should be flipped"); XCTAssertTrue(bubbleImage.messageBubbleImage.resizingMode == UIImageResizingModeStretch, @"Image should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleImage.capInsets, capInsets), @"Image capInsets should be equal to capInsets"); XCTAssertNotNil(bubbleImage.messageBubbleHighlightedImage, @"Highlighted image should not be nil"); XCTAssertEqual(bubbleImage.messageBubbleHighlightedImage.scale, bubble.scale, @"HighlightedImage scale should equal bubble image scale"); XCTAssertTrue(bubbleImage.messageBubbleHighlightedImage.resizingMode == UIImageResizingModeStretch, @"HighlightedImage should be stretchable"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(bubbleImage.messageBubbleHighlightedImage.capInsets, capInsets), @"HighlightedImage capInsets should be equal to capInsets"); } @end ================================================ FILE: JSQMessagesTests/FactoryTests/JSQMessagesMediaViewBubbleImageMaskerTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesMediaViewBubbleImageMasker.h" @interface JSQMessagesMediaViewBubbleImageMaskerTests : XCTestCase @end @implementation JSQMessagesMediaViewBubbleImageMaskerTests - (void)testMediaViewBubbleImageMasker { // GIVEN: a new masker object JSQMessagesMediaViewBubbleImageMasker *masker = [[JSQMessagesMediaViewBubbleImageMasker alloc] init]; XCTAssertNotNil(masker); // WHEN: we apply a mask to a view UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; // THEN: it succeeds without an error XCTAssertNoThrow([masker applyOutgoingBubbleImageMaskToMediaView:view1]); XCTAssertNoThrow([masker applyIncomingBubbleImageMaskToMediaView:view2]); } @end ================================================ FILE: JSQMessagesTests/FactoryTests/JSQMessagesTimestampFormatterTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesTimestampFormatter.h" @interface JSQMessagesTimestampFormatterTests : XCTestCase @end @implementation JSQMessagesTimestampFormatterTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testTimestampFormatterInit { JSQMessagesTimestampFormatter *formatter = [JSQMessagesTimestampFormatter sharedFormatter]; XCTAssertNotNil(formatter, @"Formatter should not be nil"); XCTAssertEqualObjects(formatter, [JSQMessagesTimestampFormatter sharedFormatter], @"Shared formatter should return the same instance"); UIColor *color = [UIColor lightGrayColor]; NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; paragraphStyle.alignment = NSTextAlignmentCenter; NSDictionary *dateAttrs = @{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : color, NSParagraphStyleAttributeName : paragraphStyle }; NSDictionary *timeAttrs = @{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : color, NSParagraphStyleAttributeName : paragraphStyle }; XCTAssertEqualObjects(formatter.dateTextAttributes, dateAttrs, @"Date attributes should be equal to default values"); XCTAssertEqualObjects(formatter.timeTextAttributes, timeAttrs, @"Time attributes should be equal to default values"); XCTAssertNotNil(formatter.dateFormatter, @"Property should not be nil"); } - (void)testTimestampForDate { NSDateComponents *components = [[NSDateComponents alloc] init]; [components setCalendar:[NSCalendar currentCalendar]]; [components setYear:2013]; [components setMonth:6]; [components setDay:6]; [components setHour:19]; [components setMinute:6]; [components setSecond:0]; NSDate *date = [components date]; NSString *timestampString = [[JSQMessagesTimestampFormatter sharedFormatter] timestampForDate:date]; XCTAssertEqualObjects(timestampString, @"Jun 6, 2013, 7:06 PM", @"Timestamp string should return expected value"); NSAttributedString *timestampAttributedString = [[JSQMessagesTimestampFormatter sharedFormatter] attributedTimestampForDate:date]; XCTAssertEqualObjects([timestampAttributedString string], @"Jun 6, 2013 7:06 PM", @"Attributed timestamp string should return expected value"); } - (void)testTimeForDate { NSDateComponents *components = [[NSDateComponents alloc] init]; [components setCalendar:[NSCalendar currentCalendar]]; [components setHour:7]; [components setMinute:6]; [components setSecond:0]; NSDate *date = [components date]; NSString *timeForDateString = [[JSQMessagesTimestampFormatter sharedFormatter] timeForDate:date]; XCTAssertEqualObjects(timeForDateString, @"7:06 AM", @"Time string should return expected value"); } - (void)testRelativeDataForDate { NSDate *date = [NSDate date]; NSString *relativeDateString = [[JSQMessagesTimestampFormatter sharedFormatter] relativeDateForDate:date]; XCTAssertEqualObjects(relativeDateString, @"Today", @"Relative date string should return expected value"); } @end ================================================ FILE: JSQMessagesTests/FactoryTests/JSQMessagesToolbarButtonFactoryTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesToolbarButtonFactory.h" #import "UIColor+JSQMessages.h" @interface JSQMessagesToolbarButtonFactoryTests : XCTestCase @property (strong, nonatomic) JSQMessagesToolbarButtonFactory *factory; @property (strong, nonatomic) UIFont *factoryFont; @end @implementation JSQMessagesToolbarButtonFactoryTests - (void)setUp { [super setUp]; self.factoryFont = [UIFont systemFontOfSize:15.0]; self.factory = [[JSQMessagesToolbarButtonFactory alloc] initWithFont:self.factoryFont]; } - (void)tearDown { [super tearDown]; self.factoryFont = nil; self.factory = nil; } - (void)testDefaultSendButtonItem { UIButton *button = [self.factory defaultSendButtonItem]; XCTAssertNotNil(button, @"Button should not be nil"); XCTAssertEqual(button.titleLabel.font, self.factoryFont, @"Button should use font provided by factory"); } - (void)testDefaultAccessoryButtonItem { UIButton *button = [self.factory defaultAccessoryButtonItem]; XCTAssertNotNil(button, @"Button should not be nil"); XCTAssertEqual(button.titleLabel.font, self.factoryFont, @"Button should use font provided by factory"); } @end ================================================ FILE: JSQMessagesTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: JSQMessagesTests/LayoutTests/JSQMessagesCollectionViewFlowLayoutTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesCollectionViewFlowLayout.h" @interface JSQMessagesCollectionViewFlowLayoutTests : XCTestCase @end @implementation JSQMessagesCollectionViewFlowLayoutTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testFlowLayoutInit { JSQMessagesCollectionViewFlowLayout *layout = [[JSQMessagesCollectionViewFlowLayout alloc] init]; XCTAssertNotNil(layout, @"Layout should not be nil"); } @end ================================================ FILE: JSQMessagesTests/LayoutTests/JSQMessagesCollectionViewLayoutAttributesTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesCollectionViewLayoutAttributes.h" @interface JSQMessagesCollectionViewLayoutAttributesTests : XCTestCase @end @implementation JSQMessagesCollectionViewLayoutAttributesTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testLayoutAttributesInitAndIsEqual { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; JSQMessagesCollectionViewLayoutAttributes *attrs = [JSQMessagesCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attrs.messageBubbleFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; attrs.messageBubbleContainerViewWidth = 40.0f; attrs.textViewTextContainerInsets = UIEdgeInsetsMake(10.0f, 8.0f, 10.0f, 8.0f); attrs.textViewFrameInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 6.0f); attrs.incomingAvatarViewSize = CGSizeMake(34.0f, 34.0f); attrs.outgoingAvatarViewSize = CGSizeZero; attrs.cellTopLabelHeight = 20.0f; attrs.messageBubbleTopLabelHeight = 10.0f; attrs.cellBottomLabelHeight = 15.0f; XCTAssertNotNil(attrs, @"Layout attributes should not be nil"); JSQMessagesCollectionViewLayoutAttributes *copy = [attrs copy]; XCTAssertEqualObjects(attrs, copy, @"Copied attributes should be equal"); XCTAssertEqual([attrs hash], [copy hash], @"Copied attributes hashes should be equal"); } @end ================================================ FILE: JSQMessagesTests/ModelTests/JSQAudioMediaItemTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQAudioMediaItem.h" @interface JSQAudioMediaItemTests : XCTestCase @end @implementation JSQAudioMediaItemTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testAudioItemInit { JSQAudioMediaItem *item = [[JSQAudioMediaItem alloc] initWithData:[NSData data]]; XCTAssertNotNil(item); } - (void)testAudioItemIsEqual { NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"]; JSQAudioMediaItem *item = [[JSQAudioMediaItem alloc] initWithData:[NSData dataWithContentsOfFile:sample]]; JSQAudioMediaItem *copy = [item copy]; XCTAssertEqualObjects(item, copy, @"Copied items should be equal"); XCTAssertEqual([item hash], [copy hash], @"Copied item hashes should be equal"); XCTAssertEqualObjects(item, item, @"Item should be equal to itself"); } - (void)testAudioItemArchiving { NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"]; JSQAudioMediaItem *item = [[JSQAudioMediaItem alloc] initWithData:[NSData dataWithContentsOfFile:sample]]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:item]; JSQAudioMediaItem *unarchivedItem = [NSKeyedUnarchiver unarchiveObjectWithData:data]; XCTAssertEqualObjects(item, unarchivedItem); } - (void)testMediaDataProtocol { JSQAudioMediaItem *item = [[JSQAudioMediaItem alloc] init]; XCTAssertTrue(!CGSizeEqualToSize([item mediaViewDisplaySize], CGSizeZero)); XCTAssertNotNil([item mediaPlaceholderView]); XCTAssertNil([item mediaView], @"Media view should be nil if image is nil"); NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"]; item.audioData = [NSData dataWithContentsOfFile:sample]; XCTAssertNotNil([item mediaView], @"Media view should NOT be nil once item has media data"); } @end ================================================ FILE: JSQMessagesTests/ModelTests/JSQLocationMediaItemTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQLocationMediaItem.h" #import @interface JSQLocationMediaItemTests : XCTestCase @property (strong, nonatomic) CLLocation *location; @end @implementation JSQLocationMediaItemTests - (void)setUp { [super setUp]; self.location = [[CLLocation alloc] initWithLatitude:37.795313 longitude:-122.393757]; } - (void)tearDown { self.location = nil; [super tearDown]; } - (void)testLocationItemInit { JSQLocationMediaItem *item = [[JSQLocationMediaItem alloc] initWithLocation:self.location]; XCTAssertNotNil(item); } - (void)testMediaDataProtocol { JSQLocationMediaItem *item = [[JSQLocationMediaItem alloc] init]; XCTAssertTrue(!CGSizeEqualToSize([item mediaViewDisplaySize], CGSizeZero)); XCTAssertNotNil([item mediaPlaceholderView]); XCTAssertNil([item mediaView], @"Media view should be nil if location is nil"); XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"%s", __PRETTY_FUNCTION__]]; [item setLocation:self.location withCompletionHandler:^{ [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:15 handler:^(NSError *error) { XCTAssertNil(error, @"Expectation should not error"); }]; XCTAssertNotNil([item mediaView], @"Media view should NOT be nil once item has media data"); } - (void)testCopyableItemInMediaProtocol { JSQLocationMediaItem *item = [[JSQLocationMediaItem alloc] initWithLocation:self.location]; XCTAssertNotNil(item); XCTAssertEqualObjects((NSString *)kUTTypeURL, [item mediaDataType]); NSURL *locationURL = [[NSURL alloc] initWithString:@"http://maps.apple.com/?ll=37.795313,-122.393757&z=18&q=%20"]; XCTAssertEqualObjects(locationURL, [item mediaData]); } @end ================================================ FILE: JSQMessagesTests/ModelTests/JSQMessageMediaTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import //#import #import "JSQMessage.h" // Fake media object for testing @interface FakeMedia : NSObject @end @implementation FakeMedia - (UIView *)mediaView { return [UIView new]; } - (UIView *)mediaPlaceholderView { return [self mediaView]; } - (CGSize)mediaViewDisplaySize { return CGSizeMake(50, 50); } - (void)encodeWithCoder:(NSCoder *)aCoder { } - (id)initWithCoder:(NSCoder *)aDecoder { return [self init]; } - (BOOL)isEqual:(id)object { return YES; } - (NSUInteger)hash { return 10000; } - (NSUInteger)mediaHash { return self.hash; } @end @interface JSQMessageMediaTests : XCTestCase @property (strong, nonatomic) NSString *senderId; @property (strong, nonatomic) NSString *senderDisplayName; @property (strong, nonatomic) NSDate *date; @property (strong, nonatomic) FakeMedia *fakeMediaData; @end @implementation JSQMessageMediaTests - (void)setUp { [super setUp]; self.senderId = @"324543-43556-212343"; self.senderDisplayName = @"Jesse Squires"; self.date = [NSDate date]; self.fakeMediaData = [FakeMedia new]; } - (void)tearDown { self.senderId = nil; self.senderDisplayName = nil; self.date = nil; self.fakeMediaData = nil; [super tearDown]; } - (void)testMediaMessageInit { JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date media:self.fakeMediaData]; XCTAssertNotNil(msg, @"Message should not be nil"); } - (void)testMediaMessageIsEqual { JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date media:self.fakeMediaData]; JSQMessage *copy = [msg copy]; XCTAssertEqualObjects(msg, copy, @"Copied messages should be equal"); XCTAssertEqual([msg hash], [copy hash], @"Copied messages hashes should be equal"); XCTAssertEqualObjects(msg, msg, @"Messages should be equal to itself"); } - (void)testMediaMessageArchiving { JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date media:[FakeMedia new]]; NSData *msgData = [NSKeyedArchiver archivedDataWithRootObject:msg]; JSQMessage *unarchivedMsg = [NSKeyedUnarchiver unarchiveObjectWithData:msgData]; XCTAssertEqualObjects(msg, unarchivedMsg, @"Message should be equal"); } @end ================================================ FILE: JSQMessagesTests/ModelTests/JSQMessageTextTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessage.h" @interface JSQMessageTextTests : XCTestCase @property (strong, nonatomic) NSString *senderId; @property (strong, nonatomic) NSString *senderDisplayName; @property (strong, nonatomic) NSDate *date; @property (strong, nonatomic) NSString *text; @end @implementation JSQMessageTextTests - (void)setUp { [super setUp]; self.senderId = @"324543-43556-212343"; self.senderDisplayName = @"Jesse Squires"; self.date = [NSDate date]; self.text = @"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque" @"laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi" @"architecto beatae vitae dicta sunt explicabo."; } - (void)tearDown { self.senderId = nil; self.senderDisplayName = nil; self.date = nil; self.text = nil; [super tearDown]; } #pragma mark - Text messages - (void)testTextMessageInit { JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date text:self.text]; XCTAssertNotNil(msg, @"Message should not be nil"); } - (void)testTextMessageIsEqual { JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date text:self.text]; JSQMessage *copy = [msg copy]; XCTAssertEqualObjects(msg, copy, @"Copied messages should be equal"); XCTAssertEqual([msg hash], [copy hash], @"Copied messages hashes should be equal"); XCTAssertEqualObjects(msg, msg, @"Messages should be equal to itself"); } - (void)testTextMessageArchiving { JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date text:self.text]; NSData *msgData = [NSKeyedArchiver archivedDataWithRootObject:msg]; JSQMessage *unarchivedMsg = [NSKeyedUnarchiver unarchiveObjectWithData:msgData]; XCTAssertEqualObjects(msg, unarchivedMsg, @"Message should be equal"); } @end ================================================ FILE: JSQMessagesTests/ModelTests/JSQMessagesAvatarImageTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesAvatarImage.h" @interface JSQMessagesAvatarImageTests : XCTestCase @end @implementation JSQMessagesAvatarImageTests - (void)testInitValid { UIImage *mockImage = [UIImage imageNamed:@"demo_avatar_jobs"]; JSQMessagesAvatarImage *avatar = [JSQMessagesAvatarImage avatarImageWithPlaceholder:mockImage]; XCTAssertNotNil(avatar, @"Valid init should succeed"); JSQMessagesAvatarImage *avatar2 = [JSQMessagesAvatarImage avatarWithImage:mockImage]; XCTAssertNotNil(avatar2, @"Valid init should succeed"); XCTAssertEqualObjects(avatar2.avatarImage, avatar2.avatarHighlightedImage); XCTAssertEqualObjects(avatar2.avatarHighlightedImage, avatar2.avatarPlaceholderImage); } - (void)testCopy { UIImage *mockImage = [UIImage imageNamed:@"demo_avatar_jobs"]; JSQMessagesAvatarImage *avatar = [[JSQMessagesAvatarImage alloc] initWithAvatarImage:mockImage highlightedImage:mockImage placeholderImage:mockImage]; JSQMessagesAvatarImage *copy = [avatar copy]; XCTAssertNotNil(copy, @"Copy should succeed"); XCTAssertFalse(avatar == copy, @"Copy should return new, distinct instance"); XCTAssertNotEqualObjects(avatar.avatarImage, copy.avatarImage, @"Images should not be equal"); XCTAssertNotEqual(avatar.avatarImage, copy.avatarImage, @"Images should not be equal"); XCTAssertNotEqualObjects(avatar.avatarHighlightedImage, copy.avatarHighlightedImage, @"Images should not be equal"); XCTAssertNotEqual(avatar.avatarHighlightedImage, copy.avatarHighlightedImage, @"Images should not be equal"); XCTAssertNotEqualObjects(avatar.avatarPlaceholderImage, copy.avatarPlaceholderImage, @"Images should not be equal"); XCTAssertNotEqual(avatar.avatarPlaceholderImage, copy.avatarPlaceholderImage, @"Images should not be equal"); } @end ================================================ FILE: JSQMessagesTests/ModelTests/JSQMessagesBubbleImageTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesBubbleImage.h" #import "UIImage+JSQMessages.h" @interface JSQMessagesBubbleImageTests : XCTestCase @end @implementation JSQMessagesBubbleImageTests - (void)testInitValid { UIImage *mockImage = [UIImage jsq_bubbleCompactImage]; JSQMessagesBubbleImage *bubbleImage = [[JSQMessagesBubbleImage alloc] initWithMessageBubbleImage:mockImage highlightedImage:mockImage]; XCTAssertNotNil(bubbleImage, @"Valid init should succeed"); } - (void)testCopy { UIImage *mockImage = [UIImage jsq_bubbleCompactImage]; JSQMessagesBubbleImage *bubbleImage = [[JSQMessagesBubbleImage alloc] initWithMessageBubbleImage:mockImage highlightedImage:mockImage]; JSQMessagesBubbleImage *copy = [bubbleImage copy]; XCTAssertNotNil(copy, @"Copy should succeed"); XCTAssertFalse(bubbleImage == copy, @"Copy should return new, distinct instance"); XCTAssertNotEqualObjects(bubbleImage.messageBubbleImage, copy.messageBubbleImage, @"Images should not be equal"); XCTAssertNotEqual(bubbleImage.messageBubbleImage, copy.messageBubbleImage, @"Images should not be equal"); XCTAssertNotEqualObjects(bubbleImage.messageBubbleHighlightedImage, copy.messageBubbleHighlightedImage, @"Images should not be equal"); XCTAssertNotEqual(bubbleImage.messageBubbleHighlightedImage, copy.messageBubbleHighlightedImage, @"Images should not be equal"); } @end ================================================ FILE: JSQMessagesTests/ModelTests/JSQPhotoMediaItemTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQPhotoMediaItem.h" #import @interface JSQPhotoMediaItemTests : XCTestCase @end @implementation JSQPhotoMediaItemTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testPhotoItemInit { JSQPhotoMediaItem *item = [[JSQPhotoMediaItem alloc] initWithImage:[UIImage new]]; XCTAssertNotNil(item); } - (void)testPhotoItemIsEqual { JSQPhotoMediaItem *item = [[JSQPhotoMediaItem alloc] initWithImage:[UIImage imageNamed:@"demo_avatar_jobs"]]; JSQPhotoMediaItem *copy = [item copy]; XCTAssertEqualObjects(item, copy, @"Copied items should be equal"); XCTAssertEqual([item hash], [copy hash], @"Copied item hashes should be equal"); XCTAssertEqualObjects(item, item, @"Item should be equal to itself"); } - (void)testPhotoItemArchiving { JSQPhotoMediaItem *item = [[JSQPhotoMediaItem alloc] initWithImage:[UIImage new]]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:item]; JSQPhotoMediaItem *unarchivedItem = [NSKeyedUnarchiver unarchiveObjectWithData:data]; XCTAssertEqualObjects(item, unarchivedItem); } - (void)testMediaDataProtocol { JSQPhotoMediaItem *item = [[JSQPhotoMediaItem alloc] initWithImage:nil]; XCTAssertTrue(!CGSizeEqualToSize([item mediaViewDisplaySize], CGSizeZero)); XCTAssertNotNil([item mediaPlaceholderView]); XCTAssertNil([item mediaView], @"Media view should be nil if image is nil"); item.image = [UIImage imageNamed:@"demo_avatar_jobs"]; XCTAssertNotNil([item mediaView], @"Media view should NOT be nil once item has media data"); } - (void)testCopyableItemInMediaProtocol { JSQPhotoMediaItem *item = [[JSQPhotoMediaItem alloc] initWithImage:[UIImage imageNamed:@"demo_avatar_jobs"]]; XCTAssertNotNil(item); XCTAssertEqual([item mediaDataType], (NSString *)kUTTypeJPEG); UIImage *itemImage = [[UIImage alloc] initWithData:[item mediaData]]; XCTAssertNotNil(itemImage); } @end ================================================ FILE: JSQMessagesTests/ModelTests/JSQVideoMediaItemTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQVideoMediaItem.h" @interface JSQVideoMediaItemTests : XCTestCase @end @implementation JSQVideoMediaItemTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testVideoMediaItemInit { JSQVideoMediaItem *item = [[JSQVideoMediaItem alloc] initWithFileURL:[NSURL URLWithString:@"file://"] isReadyToPlay:NO]; XCTAssertNotNil(item); } - (void)testVideoItemIsEqual { JSQVideoMediaItem *item = [[JSQVideoMediaItem alloc] initWithFileURL:[NSURL URLWithString:@"file://"] isReadyToPlay:YES]; JSQVideoMediaItem *copy = [item copy]; XCTAssertEqualObjects(item, copy, @"Copied items should be equal"); XCTAssertEqual([item hash], [copy hash], @"Copied item hashes should be equal"); XCTAssertEqualObjects(item, item, @"Item should be equal to itself"); } - (void)testVideoItemArchiving { JSQVideoMediaItem *item = [[JSQVideoMediaItem alloc] initWithFileURL:[NSURL URLWithString:@"file://"] isReadyToPlay:YES]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:item]; JSQVideoMediaItem *unarchivedItem = [NSKeyedUnarchiver unarchiveObjectWithData:data]; XCTAssertEqualObjects(item, unarchivedItem); } - (void)testMediaDataProtocol { JSQVideoMediaItem *item = [[JSQVideoMediaItem alloc] init]; XCTAssertTrue(!CGSizeEqualToSize([item mediaViewDisplaySize], CGSizeZero)); XCTAssertNotNil([item mediaPlaceholderView]); XCTAssertNil([item mediaView], @"Media view should be nil if fileURL is nil, and readyToPlay is NO"); item.fileURL = [NSURL URLWithString:@"file://"]; item.isReadyToPlay = YES; XCTAssertNotNil([item mediaView], @"Media view should NOT be nil once item has media data"); } @end ================================================ FILE: JSQMessagesTests/ViewTests/JSQMessagesCollectionViewCellTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" @interface JSQMessagesCollectionViewCellTests : XCTestCase @end @implementation JSQMessagesCollectionViewCellTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testMessagesIncomingCollectionViewCellInit { UINib *incomingCell = [JSQMessagesCollectionViewCellIncoming nib]; XCTAssertNotNil(incomingCell, @"Nib should not be nil"); NSString *incomingCellId = [JSQMessagesCollectionViewCellIncoming cellReuseIdentifier]; XCTAssertNotNil(incomingCellId, @"Cell identifier should not be nil"); XCTAssertEqualObjects(incomingCellId, NSStringFromClass([JSQMessagesCollectionViewCellIncoming class])); } - (void)testMessagesOutgoingCollectionViewCellInit { UINib *outgoingCell = [JSQMessagesCollectionViewCellOutgoing nib]; XCTAssertNotNil(outgoingCell, @"Nib should not be nil"); NSString *outgoingCellId = [JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]; XCTAssertNotNil(outgoingCellId, @"Cell identifier should not be nil"); XCTAssertEqualObjects(outgoingCellId, NSStringFromClass([JSQMessagesCollectionViewCellOutgoing class])); } @end ================================================ FILE: JSQMessagesTests/ViewTests/JSQMessagesCollectionViewTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesCollectionView.h" #import "JSQMessagesCollectionViewFlowLayout.h" @interface JSQMessagesCollectionViewTests : XCTestCase @end @implementation JSQMessagesCollectionViewTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testCollectionViewInit { JSQMessagesCollectionViewFlowLayout *layout = [[JSQMessagesCollectionViewFlowLayout alloc] init]; JSQMessagesCollectionView *view = [[JSQMessagesCollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout]; XCTAssertNotNil(view, @"Collection view should not be nil"); XCTAssertEqualObjects(view.backgroundColor, [UIColor whiteColor], @"Property should be equal to default value"); XCTAssertEqual(view.keyboardDismissMode, UIScrollViewKeyboardDismissModeInteractive, @"Property should be equal to default value"); XCTAssertEqual(view.alwaysBounceVertical, YES, @"Property should be equal to default value"); XCTAssertEqual(view.bounces, YES, @"Property should be equal to default value"); } @end ================================================ FILE: JSQMessagesTests/ViewTests/JSQMessagesComposerTextViewTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesComposerTextView.h" @interface JSQMessagesComposerTextViewTests : XCTestCase @property (strong, nonatomic) JSQMessagesComposerTextView *textView; @end @implementation JSQMessagesComposerTextViewTests - (void)setUp { [super setUp]; self.textView = [[JSQMessagesComposerTextView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 50.0f) textContainer:[NSTextContainer new]]; } - (void)tearDown { self.textView = nil; [super tearDown]; } - (void)testComposerTextViewInit { XCTAssertNotNil(self.textView, @"Text view should not be nil"); XCTAssertNil(self.textView.text, @"Property should be equal to default value"); XCTAssertNil(self.textView.placeHolder, @"Property should be equal to default value"); XCTAssertEqualObjects(self.textView.placeHolderTextColor, [UIColor lightGrayColor], @"Property should be equal to default value"); XCTAssertEqualObjects(self.textView.backgroundColor, [UIColor whiteColor], @"Property should be equal to default value"); XCTAssertEqual(self.textView.layer.borderWidth, 0.5f, @"Property should be equal to default value"); XCTAssertEqual(self.textView.layer.borderColor, [UIColor lightGrayColor].CGColor, @"Property should be equal to default value"); XCTAssertEqual(self.textView.layer.cornerRadius, 6.0f, @"Property should be equal to default value"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.textView.scrollIndicatorInsets, UIEdgeInsetsMake(6.0f, 0.0f, 6.0f, 0.0f)), @"Property should be equal to default value"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.textView.textContainerInset, UIEdgeInsetsMake(4.0f, 2.0f, 4.0f, 2.0f)), @"Property should be equal to default value"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.textView.contentInset, UIEdgeInsetsMake(1.0f, 0.0f, 1.0f, 0.0f)), @"Property should be equal to default value"); XCTAssertEqual(self.textView.scrollEnabled, YES, @"Property should be equal to default value"); XCTAssertEqual(self.textView.scrollsToTop, NO, @"Property should be equal to default value"); XCTAssertEqual(self.textView.userInteractionEnabled, YES, @"Property should be equal to default value"); XCTAssertEqual(self.textView.contentMode, UIViewContentModeRedraw, @"Property should be equal to default value"); XCTAssertEqual(self.textView.dataDetectorTypes, UIDataDetectorTypeNone, @"Property should be equal to default value"); XCTAssertEqual(self.textView.keyboardAppearance, UIKeyboardAppearanceDefault, @"Property should be equal to default value"); XCTAssertEqual(self.textView.keyboardType, UIKeyboardTypeDefault, @"Property should be equal to default value"); XCTAssertEqual(self.textView.returnKeyType, UIReturnKeyDefault, @"Property should be equal to default value"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.textView.placeHolderInsets, UIEdgeInsetsMake(5.0, 7.0, 5.0, 7.0)), @"Property should be equal to default value"); } - (void)testComposerTextViewPlaceholderInsets { // New insets to draw the placeholder UIEdgeInsets placeholderInsets = UIEdgeInsetsMake(2.0, 4.0, 2.0, 0.0); // Set the new insets into the contentView self.textView.placeHolderInsets = placeholderInsets; // Validate if placeholderInsets setter worked. We may validate the draw... XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.textView.placeHolderInsets, placeholderInsets), @"Property placeholderInsets should have changed to (2.0, 4.0, 2.0, 0.0)"); } @end ================================================ FILE: JSQMessagesTests/ViewTests/JSQMessagesInputToolbarTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesViewController.h" #import "JSQMessagesInputToolbar.h" #import "DemoMessagesViewController.h" @interface JSQMessagesInputToolbarTests : XCTestCase @end @implementation JSQMessagesInputToolbarTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testInputToolbarInit { 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.sendButtonLocation, JSQMessagesInputSendButtonLocationRight, @"Property should be equal to default value"); } // TODO: investigate this later - (void)disabled_testSetMaximumHeight { UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; XCTAssertNotNil(mainSB, @"Storyboard should not be nil"); DemoMessagesViewController *demoVC = [mainSB instantiateViewControllerWithIdentifier:@"DemoVC"]; [demoVC beginAppearanceTransition:YES animated:NO]; [demoVC endAppearanceTransition]; XCTAssertEqual(demoVC.inputToolbar.maximumHeight, NSNotFound, @"maximumInputToolbarHeight should equal default value"); demoVC.inputToolbar.maximumHeight = 54; CGRect newBounds = demoVC.inputToolbar.bounds; newBounds.size.height = 100; demoVC.inputToolbar.bounds = newBounds; XCTAssertEqual(CGRectGetHeight(demoVC.inputToolbar.bounds), 100); [demoVC.view setNeedsUpdateConstraints]; [demoVC.view setNeedsLayout]; [demoVC.view layoutIfNeeded]; XCTAssertLessThanOrEqual(CGRectGetHeight(demoVC.inputToolbar.frame), 54, @"Toolbar height should be <= to maximumInputToolbarHeight"); } @end ================================================ FILE: JSQMessagesTests/ViewTests/JSQMessagesLabelTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesLabel.h" @interface JSQMessagesLabelTests : XCTestCase @end @implementation JSQMessagesLabelTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testMessagesLabelInit { JSQMessagesLabel *label = [[JSQMessagesLabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 200.0f, 40.0f)]; XCTAssertNotNil(label, @"Label should not be nil"); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(label.textInsets, UIEdgeInsetsZero), @"Property should be equal to default value"); } @end ================================================ FILE: JSQMessagesTests/ViewTests/JSQMessagesLoadEarlierHeaderViewTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesLoadEarlierHeaderView.h" @interface JSQMessagesLoadEarlierHeaderViewTests : XCTestCase @end @implementation JSQMessagesLoadEarlierHeaderViewTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testLoadEarlierHeaderViewInit { UINib *headerView = [JSQMessagesLoadEarlierHeaderView nib]; XCTAssertNotNil(headerView, @"Nib should not be nil"); NSString *headerId = [JSQMessagesLoadEarlierHeaderView headerReuseIdentifier]; XCTAssertNotNil(headerId, @"Header view identifier should not be nil"); } @end ================================================ FILE: JSQMessagesTests/ViewTests/JSQMessagesToolbarContentViewTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesToolbarContentView.h" #import "JSQMessagesComposerTextView.h" @interface JSQMessagesToolbarContentViewTests : XCTestCase @property (strong, nonatomic) JSQMessagesToolbarContentView *contentView; @end @implementation JSQMessagesToolbarContentViewTests - (void)setUp { [super setUp]; UINib *contentViewNib = [JSQMessagesToolbarContentView nib]; XCTAssertNotNil(contentViewNib, @"Nib should not be nil"); NSArray *view = [contentViewNib instantiateWithOwner:nil options:nil]; self.contentView = [view firstObject]; XCTAssertNotNil(self.contentView, @"Content view should not be nil"); } - (void)tearDown { self.contentView = nil; [super tearDown]; } - (void)testToolbarContentViewInit { XCTAssertTrue(CGRectEqualToRect(self.contentView.frame, CGRectMake(0.0f, 0.0f, 320.0f, 44.0f)), @"Frame should be equal to default value"); XCTAssertNotNil(self.contentView.textView, @"Text view should not be nil"); XCTAssertTrue([self.contentView.textView isKindOfClass:[JSQMessagesComposerTextView class]], @"Text view should be a %@", [JSQMessagesComposerTextView class]); XCTAssertNil(self.contentView.leftBarButtonItem, @"Property should be equal to default value"); XCTAssertNil(self.contentView.rightBarButtonItem, @"Property should be equal to default value"); } @end ================================================ FILE: JSQMessagesTests/ViewTests/JSQMessagesTypingIndicatorFooterViewTests.m ================================================ // // Created by Jesse Squires // http://www.jessesquires.com // // // MIT License // Copyright (c) 2014 Jesse Squires // http://opensource.org/licenses/MIT // #import #import "JSQMessagesTypingIndicatorFooterView.h" @interface JSQMessagesTypingIndicatorFooterViewTests : XCTestCase @end @implementation JSQMessagesTypingIndicatorFooterViewTests - (void)setUp { [super setUp]; } - (void)tearDown { [super tearDown]; } - (void)testTypingIndicatorFooterViewInit { UINib *footerView = [JSQMessagesTypingIndicatorFooterView nib]; XCTAssertNotNil(footerView, @"Nib should not be nil"); NSString *footerId = [JSQMessagesTypingIndicatorFooterView footerReuseIdentifier]; XCTAssertNotNil(footerId, @"Footer view identifier should not be nil"); } @end ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Base.lproj/JSQMessages.strings ================================================ // // 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" = "Load Earlier Messages"; "send" = "Send"; "new_message" = "New Message"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: media message"; "accessory_button_accessibility_label" = "Share media"; "new_message_received_accessibility_announcement" = "New message received"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ar.lproj/JSQMessages.strings ================================================ // // 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" = "رسالة جديدة"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/bs.lproj/JSQMessages.strings ================================================ // // 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" = "Učitaj ranije poruke"; "send" = "Šalji"; "new_message" = "Nova poruka"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: medijska poruka"; "accessory_button_accessibility_label" = "Podijeli medij"; "new_message_received_accessibility_announcement" = "Primljena nova poruka"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/cs.lproj/JSQMessages.strings ================================================ // // 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" = "Načíst starší zprávy"; "send" = "Odeslat"; "new_message" = "Nová zpráva"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: multimediální zpráva"; "accessory_button_accessibility_label" = "Sdílet"; "new_message_received_accessibility_announcement" = "Přijata nová zpráva"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/da.lproj/JSQMessages.strings ================================================ // // 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" = "Indlæs tidligere beskeder"; "send" = "Send"; "new_message" = "Ny besked"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: mediebesked"; "accessory_button_accessibility_label" = "Del medie"; "new_message_received_accessibility_announcement" = "Ny besked modtaget"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/de.lproj/JSQMessages.strings ================================================ // // 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" = "Ältere Nachrichten laden"; "send" = "Senden"; "new_message" = "Neue Nachricht"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: media Nachricht"; "accessory_button_accessibility_label" = "Aktien media"; "new_message_received_accessibility_announcement" = "Neue Nachricht empfangen"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/en.lproj/JSQMessages.strings ================================================ // // 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" = "Load Earlier Messages"; "send" = "Send"; "new_message" = "New Message"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: media message"; "accessory_button_accessibility_label" = "Share media"; "new_message_received_accessibility_announcement" = "New message received"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/es.lproj/JSQMessages.strings ================================================ // // 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" = "Cargar mensajes anteriores"; "send" = "Enviar"; "new_message" = "Nuevo mensaje"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: imagen"; "accessory_button_accessibility_label" = "Intercambio de archivos"; "new_message_received_accessibility_announcement" = "mensaje recibido"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fa.lproj/JSQMessages.strings ================================================ /* Localization provided by MasihTak under MIT license The Mac OS X and iOS localization experts. https://masihtak.com */ "accessory_button_accessibility_label" = "اشتراک رسانه"; "load_earlier_messages" = "بارگذاری پیام های قبلی"; "media_message_accessibility_label" = "%@: پیام رسانه ای"; "new_message" = "پیام جدید"; "new_message_received_accessibility_announcement" = "پیام جدید دریافت شد"; "send" = "ارسال"; "text_message_accessibility_label" = "%@: %@"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fi.lproj/JSQMessages.strings ================================================ // // 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" = "Lataa aiempia viestejä"; "send" = "Lähetä"; "new_message" = "Uusi viesti"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fr.lproj/JSQMessages.strings ================================================ // // 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" = "Messages précedents"; "send" = "Envoyer"; "new_message" = "Nouveau message"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: image"; "accessory_button_accessibility_label" = "Partager fichier"; "new_message_received_accessibility_announcement" = "Nouveau message reçu"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/he.lproj/JSQMessages.strings ================================================ // // 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" = "הודעה חדשה"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/hr.lproj/JSQMessages.strings ================================================ // // 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" = "Učitaj ranije poruke"; "send" = "Šalji"; "new_message" = "Nova poruka"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: medijska poruka"; "accessory_button_accessibility_label" = "Podijeli medij"; "new_message_received_accessibility_announcement" = "Primljena nova poruka"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/id.lproj/JSQMessages.strings ================================================ // // 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" = "Muat Pesan Sebelumnya"; "send" = "Kirim"; "new_message" = "Pesan Baru"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/it.lproj/JSQMessages.strings ================================================ // // 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"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ja.lproj/JSQMessages.strings ================================================ // // 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" = "新しいメッセージ"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ko.lproj/JSQMessages.strings ================================================ // // 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" = "새로운 메시지"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ms.lproj/JSQMessages.strings ================================================ // // 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" = "Muat Turun Mesej Lama"; "send" = "Hantar"; "new_message" = "Mesej Baru"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/nb.lproj/JSQMessages.strings ================================================ // // 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" = "Last tidligere beskjeder"; "send" = "Send"; "new_message" = "Ny melding"; "text_message_accessibility_label" = "%@: %@"; "media_message_accessibility_label" = "%@: mediamelding"; "accessory_button_accessibility_label" = "Del media"; "new_message_received_accessibility_announcement" = "Ny melding mottatt"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/nl.lproj/JSQMessages.strings ================================================ // // 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" = "Laad eerdere berichten"; "send" = "Stuur"; "new_message" = "Nieuw bericht"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/pl.lproj/JSQMessages.strings ================================================ // // 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ść"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/pt.lproj/JSQMessages.strings ================================================ // // 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 anteriores"; "send" = "Enviar"; "new_message" = "Nova Mensagem"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ro.lproj/JSQMessages.strings ================================================ // // 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"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ru.lproj/JSQMessages.strings ================================================ // // 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" = "Сообщение"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/sv.lproj/JSQMessages.strings ================================================ // // 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" = "Visa tidigare meddelanden"; "send" = "Skicka"; "new_message" = "Nytt meddelande"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/th.lproj/JSQMessages.strings ================================================ // // 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" = "ข้อความใหม่"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/tr.lproj/JSQMessages.strings ================================================ // // 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" = "Eski mesajları yükle"; "send" = "Gönder"; "new_message" = "Yeni Mesaj"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/vi.lproj/JSQMessages.strings ================================================ // // 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" = "Tải thêm tin nhắn"; "send" = "Gửi"; "new_message" = "Tin nhắn mới"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/zh-Hans.lproj/JSQMessages.strings ================================================ // // 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" = "新信息"; ================================================ FILE: JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/zh-Hant.lproj/JSQMessages.strings ================================================ // // 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" = "新信息"; ================================================ FILE: JSQMessagesViewController/Categories/NSBundle+JSQMessages.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN @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. */ + (nullable NSString *)jsq_localizedStringForKey:(NSString *)key; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Categories/NSBundle+JSQMessages.m ================================================ // // 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 ================================================ FILE: JSQMessagesViewController/Categories/NSString+JSQMessages.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN @interface NSString (JSQMessages) /** * @return A copy of the receiver with all leading and trailing whitespace removed. */ - (NSString *)jsq_stringByTrimingWhitespace; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Categories/NSString+JSQMessages.m ================================================ // // 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 "NSString+JSQMessages.h" @implementation NSString (JSQMessages) - (NSString *)jsq_stringByTrimingWhitespace { return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } @end ================================================ FILE: JSQMessagesViewController/Categories/UIColor+JSQMessages.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN @interface UIColor (JSQMessages) #pragma mark - Message bubble colors /** * @return A color object containing HSB values similar to the iOS 7 messages app green bubble color. */ + (UIColor *)jsq_messageBubbleGreenColor; /** * @return A color object containing HSB values similar to the iOS 7 messages app blue bubble color. */ + (UIColor *)jsq_messageBubbleBlueColor; /** * @return A color object containing HSB values similar to the iOS 7 red color. */ + (UIColor *)jsq_messageBubbleRedColor; /** * @return A color object containing HSB values similar to the iOS 7 messages app light gray bubble color. */ + (UIColor *)jsq_messageBubbleLightGrayColor; #pragma mark - Utilities /** * Creates and returns a new color object whose brightness component is decreased by the given value, using the initial color values of the receiver. * * @param value A floating point value describing the amount by which to decrease the brightness of the receiver. * * @return A new color object whose brightness is decreased by the given values. The other color values remain the same as the receiver. */ - (UIColor *)jsq_colorByDarkeningColorWithValue:(CGFloat)value; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Categories/UIColor+JSQMessages.m ================================================ // // 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 "UIColor+JSQMessages.h" @implementation UIColor (JSQMessages) #pragma mark - Message bubble colors + (UIColor *)jsq_messageBubbleGreenColor { return [UIColor colorWithHue:130.0f / 360.0f saturation:0.68f brightness:0.84f alpha:1.0f]; } + (UIColor *)jsq_messageBubbleBlueColor { return [UIColor colorWithHue:210.0f / 360.0f saturation:0.94f brightness:1.0f alpha:1.0f]; } + (UIColor *)jsq_messageBubbleRedColor { return [UIColor colorWithHue:0.0f / 360.0f saturation:0.79f brightness:1.0f alpha:1.0f]; } + (UIColor *)jsq_messageBubbleLightGrayColor { return [UIColor colorWithHue:240.0f / 360.0f saturation:0.02f brightness:0.92f alpha:1.0f]; } #pragma mark - Utilities - (UIColor *)jsq_colorByDarkeningColorWithValue:(CGFloat)value { NSUInteger totalComponents = CGColorGetNumberOfComponents(self.CGColor); BOOL isGreyscale = (totalComponents == 2) ? YES : NO; CGFloat *oldComponents = (CGFloat *)CGColorGetComponents(self.CGColor); CGFloat newComponents[4]; if (isGreyscale) { newComponents[0] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; newComponents[1] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; newComponents[2] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; newComponents[3] = oldComponents[1]; } else { newComponents[0] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; newComponents[1] = oldComponents[1] - value < 0.0f ? 0.0f : oldComponents[1] - value; newComponents[2] = oldComponents[2] - value < 0.0f ? 0.0f : oldComponents[2] - value; newComponents[3] = oldComponents[3]; } CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGColorRef newColor = CGColorCreate(colorSpace, newComponents); CGColorSpaceRelease(colorSpace); UIColor *retColor = [UIColor colorWithCGColor:newColor]; CGColorRelease(newColor); return retColor; } @end ================================================ FILE: JSQMessagesViewController/Categories/UIImage+JSQMessages.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN @interface UIImage (JSQMessages) /** * Creates and returns a new image object that is masked with the specified mask color. * * @param maskColor The color value for the mask. This value must not be `nil`. * * @return A new image object masked with the specified color. */ - (UIImage *)jsq_imageMaskedWithColor:(UIColor *)maskColor; /** * @return The regular message bubble image. */ + (UIImage *)jsq_bubbleRegularImage; /** * @return The regular message bubble image without a tail. */ + (UIImage *)jsq_bubbleRegularTaillessImage; /** * @return The regular message bubble image stroked, not filled. */ + (UIImage *)jsq_bubbleRegularStrokedImage; /** * @return The regular message bubble image stroked, not filled and without a tail. */ + (UIImage *)jsq_bubbleRegularStrokedTaillessImage; /** * @return The compact message bubble image. * * @discussion This is the default bubble image used by `JSQMessagesBubbleImageFactory`. */ + (UIImage *)jsq_bubbleCompactImage; /** * @return The compact message bubble image without a tail. */ + (UIImage *)jsq_bubbleCompactTaillessImage; /** * @return The default input toolbar accessory image. */ + (UIImage *)jsq_defaultAccessoryImage; /** * @return The default typing indicator image. */ + (UIImage *)jsq_defaultTypingIndicatorImage; /** * @return The default play icon image. */ + (UIImage *)jsq_defaultPlayImage; /** * @return The default pause icon image. */ + (UIImage *)jsq_defaultPauseImage; /** * @return The standard share icon image. * * @discussion This is the default icon for the message accessory button. */ + (UIImage *)jsq_shareActionImage; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Categories/UIImage+JSQMessages.m ================================================ // // 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 "UIImage+JSQMessages.h" #import "NSBundle+JSQMessages.h" @implementation UIImage (JSQMessages) - (UIImage *)jsq_imageMaskedWithColor:(UIColor *)maskColor { NSParameterAssert(maskColor != nil); CGRect imageRect = CGRectMake(0.0f, 0.0f, self.size.width, self.size.height); UIImage *newImage = nil; UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, self.scale); { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextScaleCTM(context, 1.0f, -1.0f); CGContextTranslateCTM(context, 0.0f, -(imageRect.size.height)); CGContextClipToMask(context, imageRect, self.CGImage); CGContextSetFillColorWithColor(context, maskColor.CGColor); CGContextFillRect(context, imageRect); newImage = UIGraphicsGetImageFromCurrentImageContext(); } UIGraphicsEndImageContext(); return newImage; } + (UIImage *)jsq_bubbleImageFromBundleWithName:(NSString *)name { NSBundle *bundle = [NSBundle jsq_messagesAssetBundle]; NSString *path = [bundle pathForResource:name ofType:@"png" inDirectory:@"Images"]; return [UIImage imageWithContentsOfFile:path]; } + (UIImage *)jsq_bubbleRegularImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"bubble_regular"]; } + (UIImage *)jsq_bubbleRegularTaillessImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"bubble_tailless"]; } + (UIImage *)jsq_bubbleRegularStrokedImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"bubble_stroked"]; } + (UIImage *)jsq_bubbleRegularStrokedTaillessImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"bubble_stroked_tailless"]; } + (UIImage *)jsq_bubbleCompactImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"bubble_min"]; } + (UIImage *)jsq_bubbleCompactTaillessImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"bubble_min_tailless"]; } + (UIImage *)jsq_defaultAccessoryImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"clip"]; } + (UIImage *)jsq_defaultTypingIndicatorImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"typing"]; } + (UIImage *)jsq_defaultPlayImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"play"]; } + (UIImage *)jsq_defaultPauseImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"pause"]; } + (UIImage *)jsq_shareActionImage { return [UIImage jsq_bubbleImageFromBundleWithName:@"share"]; } @end ================================================ FILE: JSQMessagesViewController/Categories/UIView+JSQMessages.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN @interface UIView (JSQMessages) /** * Pins the subview of the receiver to the edge of its frame, as specified by the given attribute, by adding a layout constraint. * * @param subview The subview to which the receiver will be pinned. * @param attribute The layout constraint attribute specifying one of `NSLayoutAttributeBottom`, `NSLayoutAttributeTop`, `NSLayoutAttributeLeading`, `NSLayoutAttributeTrailing`. */ - (void)jsq_pinSubview:(UIView *)subview toEdge:(NSLayoutAttribute)attribute; /** * Pins all edges of the specified subview to the receiver. * * @param subview The subview to which the receiver will be pinned. */ - (void)jsq_pinAllEdgesOfSubview:(UIView *)subview; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Categories/UIView+JSQMessages.m ================================================ // // 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 "UIView+JSQMessages.h" @implementation UIView (JSQMessages) - (void)jsq_pinSubview:(UIView *)subview toEdge:(NSLayoutAttribute)attribute { [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:attribute relatedBy:NSLayoutRelationEqual toItem:subview attribute:attribute multiplier:1.0f constant:0.0f]]; } - (void)jsq_pinAllEdgesOfSubview:(UIView *)subview { [self jsq_pinSubview:subview toEdge:NSLayoutAttributeBottom]; [self jsq_pinSubview:subview toEdge:NSLayoutAttributeTop]; [self jsq_pinSubview:subview toEdge:NSLayoutAttributeLeading]; [self jsq_pinSubview:subview toEdge:NSLayoutAttributeTrailing]; } @end ================================================ FILE: JSQMessagesViewController/Controllers/JSQMessagesViewController.h ================================================ // // 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 #import "JSQMessagesCollectionView.h" #import "JSQMessagesCollectionViewFlowLayout.h" #import "JSQMessagesInputToolbar.h" NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesViewController` class is an abstract class that represents a view controller whose content consists of * a `JSQMessagesCollectionView` and `JSQMessagesInputToolbar` and is specialized to display a messaging interface. * * @warning This class is intended to be subclassed. You should not use it directly. */ @interface JSQMessagesViewController : UIViewController /** * 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, nullable) JSQMessagesCollectionView *collectionView; /** * Returns the input toolbar view object managed by this view controller. * This view controller is the toolbar's delegate. */ @property (strong, nonatomic, readonly) JSQMessagesInputToolbar *inputToolbar; /** * 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. * Set to `NO` if you want to manage scrolling yourself. */ @property (assign, nonatomic) BOOL automaticallyScrollsToMostRecentMessage; /** * The collection view cell identifier to use for dequeuing outgoing message collection view cells * in the collectionView for text messages. * * @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. * You should only override this property's default value if you are proividing your own cell prototypes. * These prototypes must be registered with the collectionView for reuse and you are then responsible for * completely overriding many delegate and data source methods for the collectionView, * including `collectionView:cellForItemAtIndexPath:`. */ @property (copy, nonatomic) NSString *outgoingCellIdentifier; /** * 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. * The default value is the string returned by `[JSQMessagesCollectionViewCellOutgoing mediaCellReuseIdentifier]`. * This value must not be `nil`. * * @see JSQMessagesCollectionViewCellOutgoing. * * @warning Overriding this property's default value is *not* recommended. * You should only override this property's default value if you are proividing your own cell prototypes. * These prototypes must be registered with the collectionView for reuse and you are then responsible for * completely overriding many delegate and data source methods for the collectionView, * including `collectionView:cellForItemAtIndexPath:`. */ @property (copy, nonatomic) NSString *outgoingMediaCellIdentifier; /** * The collection view cell identifier to use for dequeuing incoming message collection view cells * in the collectionView for text messages. * * @discussion This cell identifier is used for incoming text message data items. * The default value is the string returned by `[JSQMessagesCollectionViewCellIncoming cellReuseIdentifier]`. * This value must not be `nil`. * * @see JSQMessagesCollectionViewCellIncoming. * * @warning Overriding this property's default value is *not* recommended. * You should only override this property's default value if you are proividing your own cell prototypes. * These prototypes must be registered with the collectionView for reuse and you are then responsible for * completely overriding many delegate and data source methods for the collectionView, * including `collectionView:cellForItemAtIndexPath:`. */ @property (copy, nonatomic) NSString *incomingCellIdentifier; /** * The collection view cell identifier to use for dequeuing incoming message collection view cells * in the collectionView for media messages. * * @discussion This cell identifier is used for incoming media message data items. * The default value is the string returned by `[JSQMessagesCollectionViewCellIncoming mediaCellReuseIdentifier]`. * This value must not be `nil`. * * @see JSQMessagesCollectionViewCellIncoming. * * @warning Overriding this property's default value is *not* recommended. * You should only override this property's default value if you are proividing your own cell prototypes. * These prototypes must be registered with the collectionView for reuse and you are then responsible for * completely overriding many delegate and data source methods for the collectionView, * including `collectionView:cellForItemAtIndexPath:`. */ @property (copy, nonatomic) NSString *incomingMediaCellIdentifier; /** * Specifies whether or not the view controller should show the typing indicator for an incoming message. * * @discussion Setting this property to `YES` will animate showing the typing indicator immediately. * Setting this property to `NO` will animate hiding the typing indicator immediately. You will need to scroll * to the bottom of the collection view in order to see the typing indicator. You may use `scrollToBottomAnimated:` for this. */ @property (assign, nonatomic) BOOL showTypingIndicator; /** * Specifies whether or not the view controller should show the "load earlier messages" header view. * * @discussion Setting this property to `YES` will show the header view immediately. * Settings this property to `NO` will hide the header view immediately. You will need to scroll to * the top of the collection view in order to see the header. */ @property (assign, nonatomic) BOOL showLoadEarlierMessagesHeader; /** * Specifies an additional inset amount to be added to the collectionView's `contentInset` and `scrollIndicatorInsets` value. * Currently, the `.left` and `.right` insets are ignored. * * @discussion Use this property to adjust the insets to account for a custom subview in your view controller. */ @property (assign, nonatomic) UIEdgeInsets additionalContentInset; #pragma mark - Class methods /** * Returns the `UINib` object initialized for a `JSQMessagesViewController`. * * @return The initialized `UINib` object. * * @discussion You may override this method to provide a customized nib. If you do, * you should also override `messagesViewController` to return your * view controller loaded from your custom nib. */ + (UINib *)nib; /** * Creates and returns a new `JSQMessagesViewController` object. * * @discussion This is the designated initializer for programmatic instantiation. * * @return An initialized `JSQMessagesViewController` object. */ + (instancetype)messagesViewController; #pragma mark - Messages view controller /** * This method is called when the user taps the send button on the inputToolbar * after composing a message with the specified data. * * @param button The send button that was pressed by the user. * @param text The message text. * @param senderId The message sender identifier. * @param senderDisplayName The message sender display name. * @param date The message date. */ - (void)didPressSendButton:(UIButton *)button withMessageText:(NSString *)text senderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date; /** * This method is called when the user taps the accessory button on the `inputToolbar`. * * @param sender The accessory button that was pressed by the user. */ - (void)didPressAccessoryButton:(UIButton *)sender; /** * Animates the sending of a new message. See `finishSendingMessageAnimated:` for more details. * * @see `finishSendingMessageAnimated:`. */ - (void)finishSendingMessage; /** * Completes the "sending" of a new message by resetting the `inputToolbar`, adding a new collection view cell in the collection view, * reloading the collection view, and scrolling to the newly sent message as specified by `automaticallyScrollsToMostRecentMessage`. * Scrolling to the new message can be animated as specified by the animated parameter. * * @param animated Specifies whether the sending of a message should be animated or not. Pass `YES` to animate changes, `NO` otherwise. * * @discussion You should call this method at the end of `didPressSendButton: withMessageText: senderId: senderDisplayName: date` * after adding the new message to your data source and performing any related tasks. * * @see `automaticallyScrollsToMostRecentMessage`. */ - (void)finishSendingMessageAnimated:(BOOL)animated; /** * Animates the receiving of a new message. See `finishReceivingMessageAnimated:` for more details. * * @see `finishReceivingMessageAnimated:`. */ - (void)finishReceivingMessage; /** * Completes the "receiving" of a new message by showing the typing indicator, adding a new collection view cell in the collection view, * reloading the collection view, and scrolling to the newly sent message as specified by `automaticallyScrollsToMostRecentMessage`. * Scrolling to the new message can be animated as specified by the animated parameter. * * @param animated Specifies whether the receiving of a message should be animated or not. Pass `YES` to animate changes, `NO` otherwise. * * @discussion You should call this method after adding a new "received" message to your data source and performing any related tasks. * * @see `automaticallyScrollsToMostRecentMessage`. */ - (void)finishReceivingMessageAnimated:(BOOL)animated; /** * Scrolls the collection view such that the bottom most cell is completely visible, above the `inputToolbar`. * * @param animated Pass `YES` if you want to animate scrolling, `NO` if it should be immediate. */ - (void)scrollToBottomAnimated:(BOOL)animated; /** * Used to decide if a message is incoming or outgoing. * * @discussion The default implementation of this method compares the `senderId` of the message to the * value of the `senderId` property and returns `YES` if they are equal. Subclasses can override * this method to specialize the decision logic. */ - (BOOL)isOutgoingMessage:(id)messageItem; /** * Scrolls the collection view so that the cell at the specified indexPath is completely visible above the `inputToolbar`. * * @param indexPath The indexPath for the cell that will be visible. * @param animated Pass `YES` if you want to animate scrolling, `NO` otherwise. */ - (void)scrollToIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated; /** Call to super required. */ - (void)viewDidLoad NS_REQUIRES_SUPER; /** Call to super required. */ - (void)viewWillAppear:(BOOL)animated NS_REQUIRES_SUPER; /** Call to super required. */ - (void)viewDidAppear:(BOOL)animated NS_REQUIRES_SUPER; /** Call to super required. */ - (void)viewWillDisappear:(BOOL)animated NS_REQUIRES_SUPER; /** Call to super required. */ - (void)viewDidDisappear:(BOOL)animated NS_REQUIRES_SUPER; /** Called when `UIMenuControllerWillShowMenuNotification` is posted. @param notification The posted notification. */ - (void)didReceiveMenuWillShowNotification:(NSNotification *)notification; /** Called when `UIMenuControllerWillHideMenuNotification` is posted. @param notification The posted notification. */ - (void)didReceiveMenuWillHideNotification:(NSNotification *)notification; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Controllers/JSQMessagesViewController.m ================================================ // // 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 "JSQMessagesViewController.h" #import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h" #import "JSQMessageData.h" #import "JSQMessageBubbleImageDataSource.h" #import "JSQMessageAvatarImageDataSource.h" #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" #import "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesLoadEarlierHeaderView.h" #import "NSString+JSQMessages.h" #import "NSBundle+JSQMessages.h" #import // Fixes rdar://26295020 // See issue #1247 and Peter Steinberger's comment: // https://github.com/jessesquires/JSQMessagesViewController/issues/1247#issuecomment-219386199 // Gist with workaround: https://gist.github.com/steipete/b00fc02aa9f1c66c11d0f996b1ba1265 // Forgive me static IMP JSQReplaceMethodWithBlock(Class c, SEL origSEL, id block) { NSCParameterAssert(block); // get original method Method origMethod = class_getInstanceMethod(c, origSEL); NSCParameterAssert(origMethod); // convert block to IMP trampoline and replace method implementation IMP newIMP = imp_implementationWithBlock(block); // Try adding the method if not yet in the current class if (!class_addMethod(c, origSEL, newIMP, method_getTypeEncoding(origMethod))) { return method_setImplementation(origMethod, newIMP); } else { return method_getImplementation(origMethod); } } static void JSQInstallWorkaroundForSheetPresentationIssue26295020(void) { __block void (^removeWorkaround)(void) = ^{}; const void (^installWorkaround)(void) = ^{ const SEL presentSEL = @selector(presentViewController:animated:completion:); __block IMP origIMP = JSQReplaceMethodWithBlock(UIViewController.class, presentSEL, ^(UIViewController *self, id vC, BOOL animated, id completion) { UIViewController *targetVC = self; while (targetVC.presentedViewController) { targetVC = targetVC.presentedViewController; } ((void (*)(id, SEL, id, BOOL, id))origIMP)(targetVC, presentSEL, vC, animated, completion); }); removeWorkaround = ^{ Method origMethod = class_getInstanceMethod(UIViewController.class, presentSEL); NSCParameterAssert(origMethod); class_replaceMethod(UIViewController.class, presentSEL, origIMP, method_getTypeEncoding(origMethod)); }; }; const SEL presentSheetSEL = NSSelectorFromString(@"presentSheetFromRect:"); const void (^swizzleOnClass)(Class k) = ^(Class klass) { const __block IMP origIMP = JSQReplaceMethodWithBlock(klass, presentSheetSEL, ^(id self, CGRect rect) { // Before calling the original implementation, we swizzle the presentation logic on UIViewController installWorkaround(); // UIKit later presents the sheet on [view.window rootViewController]; // See https://github.com/WebKit/webkit/blob/1aceb9ed7a42d0a5ed11558c72bcd57068b642e7/Source/WebKit2/UIProcess/ios/WKActionSheet.mm#L102 // Our workaround forwards this to the topmost presentedViewController instead. ((void (*)(id, SEL, CGRect))origIMP)(self, presentSheetSEL, rect); // Cleaning up again - this workaround would swallow bugs if we let it be there. removeWorkaround(); }); }; // _UIRotatingAlertController Class alertClass = NSClassFromString([NSString stringWithFormat:@"%@%@%@", @"_U", @"IRotat", @"ingAlertController"]); if (alertClass) { swizzleOnClass(alertClass); } // WKActionSheet Class actionSheetClass = NSClassFromString([NSString stringWithFormat:@"%@%@%@", @"W", @"KActio", @"nSheet"]); if (actionSheetClass) { swizzleOnClass(actionSheetClass); } } @interface JSQMessagesViewController () @property (weak, nonatomic) IBOutlet JSQMessagesCollectionView *collectionView; @property (strong, nonatomic) IBOutlet JSQMessagesInputToolbar *inputToolbar; @property (nonatomic) NSLayoutConstraint *toolbarHeightConstraint; @property (strong, nonatomic) NSIndexPath *selectedIndexPathForMenu; @end @implementation JSQMessagesViewController #pragma mark - Class methods + (UINib *)nib { return [UINib nibWithNibName:NSStringFromClass([JSQMessagesViewController class]) bundle:[NSBundle bundleForClass:[JSQMessagesViewController class]]]; } + (instancetype)messagesViewController { return [[[self class] alloc] initWithNibName:NSStringFromClass([JSQMessagesViewController class]) bundle:[NSBundle bundleForClass:[JSQMessagesViewController class]]]; } + (void)initialize { [super initialize]; if (self == [JSQMessagesViewController self]) { JSQInstallWorkaroundForSheetPresentationIssue26295020(); } } #pragma mark - Initialization - (void)jsq_configureMessagesViewController { self.view.backgroundColor = [UIColor whiteColor]; self.toolbarHeightConstraint.constant = self.inputToolbar.preferredDefaultHeight; 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.accessibilityLabel = [NSBundle jsq_localizedStringForKey:@"new_message"]; self.inputToolbar.contentView.textView.delegate = self; self.inputToolbar.contentView.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; [self.inputToolbar removeFromSuperview]; self.automaticallyScrollsToMostRecentMessage = YES; self.outgoingCellIdentifier = [JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]; self.outgoingMediaCellIdentifier = [JSQMessagesCollectionViewCellOutgoing mediaCellReuseIdentifier]; self.incomingCellIdentifier = [JSQMessagesCollectionViewCellIncoming cellReuseIdentifier]; self.incomingMediaCellIdentifier = [JSQMessagesCollectionViewCellIncoming mediaCellReuseIdentifier]; // NOTE: let this behavior be opt-in for now // [JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)]; self.showTypingIndicator = NO; self.showLoadEarlierMessagesHeader = NO; self.additionalContentInset = UIEdgeInsetsZero; [self jsq_updateCollectionViewInsets]; } - (void)dealloc { [self jsq_registerForNotifications:NO]; _collectionView.dataSource = nil; _collectionView.delegate = nil; _inputToolbar.contentView.textView.delegate = nil; _inputToolbar.delegate = nil; } #pragma mark - Setters - (void)setShowTypingIndicator:(BOOL)showTypingIndicator { if (_showTypingIndicator == showTypingIndicator) { return; } _showTypingIndicator = showTypingIndicator; [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; [self.collectionView.collectionViewLayout invalidateLayout]; } - (void)setShowLoadEarlierMessagesHeader:(BOOL)showLoadEarlierMessagesHeader { if (_showLoadEarlierMessagesHeader == showLoadEarlierMessagesHeader) { return; } _showLoadEarlierMessagesHeader = showLoadEarlierMessagesHeader; [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; [self.collectionView.collectionViewLayout invalidateLayout]; [self.collectionView reloadData]; } - (void)setAdditionalContentInset:(UIEdgeInsets)additionalContentInset { _additionalContentInset = additionalContentInset; [self jsq_updateCollectionViewInsets]; } #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; [[[self class] nib] instantiateWithOwner:self options:nil]; [self jsq_configureMessagesViewController]; [self jsq_registerForNotifications:YES]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (!self.inputToolbar.contentView.textView.hasText) { self.toolbarHeightConstraint.constant = self.inputToolbar.preferredDefaultHeight; } [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]]; }); } } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; self.collectionView.collectionViewLayout.springinessEnabled = NO; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; } #pragma mark - View rotation - (BOOL)shouldAutorotate { return YES; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) { return UIInterfaceOrientationMaskAllButUpsideDown; } return UIInterfaceOrientationMaskAll; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; [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]; } } - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; [self jsq_resetLayoutAndCaches]; } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { [super traitCollectionDidChange:previousTraitCollection]; [self jsq_resetLayoutAndCaches]; } - (void)jsq_resetLayoutAndCaches { JSQMessagesCollectionViewFlowLayoutInvalidationContext *context = [JSQMessagesCollectionViewFlowLayoutInvalidationContext context]; context.invalidateFlowLayoutMessagesCache = YES; [self.collectionView.collectionViewLayout invalidateLayoutWithContext:context]; } #pragma mark - Messages view controller - (void)didPressSendButton:(UIButton *)button withMessageText:(NSString *)text senderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date { NSAssert(NO, @"Error! required method not implemented in subclass. Need to implement %s", __PRETTY_FUNCTION__); } - (void)didPressAccessoryButton:(UIButton *)sender { NSAssert(NO, @"Error! required method not implemented in subclass. Need to implement %s", __PRETTY_FUNCTION__); } - (void)finishSendingMessage { [self finishSendingMessageAnimated:YES]; } - (void)finishSendingMessageAnimated:(BOOL)animated { UITextView *textView = self.inputToolbar.contentView.textView; textView.text = nil; [textView.undoManager removeAllActions]; [[NSNotificationCenter defaultCenter] postNotificationName:UITextViewTextDidChangeNotification object:textView]; [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; [self.collectionView reloadData]; if (self.automaticallyScrollsToMostRecentMessage) { [self scrollToBottomAnimated:animated]; } } - (void)finishReceivingMessage { [self finishReceivingMessageAnimated:YES]; } - (void)finishReceivingMessageAnimated:(BOOL)animated { self.showTypingIndicator = NO; [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; [self.collectionView reloadData]; if (self.automaticallyScrollsToMostRecentMessage && ![self jsq_isMenuVisible]) { [self scrollToBottomAnimated:animated]; } UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, [NSBundle jsq_localizedStringForKey:@"new_message_received_accessibility_announcement"]); } - (void)scrollToBottomAnimated:(BOOL)animated { if ([self.collectionView numberOfSections] == 0) { return; } NSIndexPath *lastCell = [NSIndexPath indexPathForItem:([self.collectionView numberOfItemsInSection:0] - 1) inSection:0]; [self scrollToIndexPath:lastCell animated:animated]; } - (void)scrollToIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated { if ([self.collectionView numberOfSections] <= indexPath.section) { return; } NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:indexPath.section]; if (numberOfItems == 0) { return; } CGFloat collectionViewContentHeight = [self.collectionView.collectionViewLayout collectionViewContentSize].height; BOOL isContentTooSmall = (collectionViewContentHeight < CGRectGetHeight(self.collectionView.bounds)); if (isContentTooSmall) { // workaround for the first few messages not scrolling // when the collection view content size is too small, `scrollToItemAtIndexPath:` doesn't work properly // this seems to be a UIKit bug, see #256 on GitHub [self.collectionView scrollRectToVisible:CGRectMake(0.0, collectionViewContentHeight - 1.0f, 1.0f, 1.0f) animated:animated]; return; } NSInteger item = MAX(MIN(indexPath.item, numberOfItems - 1), 0); indexPath = [NSIndexPath indexPathForItem:item inSection:0]; // 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 CGSize cellSize = [self.collectionView.collectionViewLayout sizeForItemAtIndexPath:indexPath]; CGFloat maxHeightForVisibleMessage = CGRectGetHeight(self.collectionView.bounds) - self.collectionView.contentInset.top - self.collectionView.contentInset.bottom - CGRectGetHeight(self.inputToolbar.bounds); UICollectionViewScrollPosition scrollPosition = (cellSize.height > maxHeightForVisibleMessage) ? UICollectionViewScrollPositionBottom : UICollectionViewScrollPositionTop; [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; } - (BOOL)isOutgoingMessage:(id)messageItem { NSString *messageSenderId = [messageItem senderId]; NSParameterAssert(messageSenderId != nil); return [messageSenderId isEqualToString:[self.collectionView.dataSource senderId]]; } #pragma mark - JSQMessages collection view data source - (NSString *)senderDisplayName { NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__); return nil; } - (NSString *)senderId { NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__); return nil; } - (id)collectionView:(JSQMessagesCollectionView *)collectionView messageDataForItemAtIndexPath:(NSIndexPath *)indexPath { NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__); return nil; } - (void)collectionView:(JSQMessagesCollectionView *)collectionView didDeleteMessageAtIndexPath:(NSIndexPath *)indexPath { NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__); } - (id)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath { NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__); return nil; } - (id)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath { NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__); return nil; } - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath { return nil; } - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath { return nil; } - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath { return nil; } #pragma mark - Collection view data source - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return 0; } - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 1; } - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { id messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath]; NSParameterAssert(messageItem != nil); BOOL isOutgoingMessage = [self isOutgoingMessage:messageItem]; BOOL isMediaMessage = [messageItem isMediaMessage]; NSString *cellIdentifier = nil; if (isMediaMessage) { cellIdentifier = isOutgoingMessage ? self.outgoingMediaCellIdentifier : self.incomingMediaCellIdentifier; } else { cellIdentifier = isOutgoingMessage ? self.outgoingCellIdentifier : self.incomingCellIdentifier; } JSQMessagesCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; cell.accessibilityIdentifier = [NSString stringWithFormat:@"(%ld, %ld)", (long)indexPath.section, (long)indexPath.row]; cell.delegate = collectionView; if (!isMediaMessage) { cell.textView.text = [messageItem text]; NSParameterAssert(cell.textView.text != nil); id bubbleImageDataSource = [collectionView.dataSource collectionView:collectionView messageBubbleImageDataForItemAtIndexPath:indexPath]; cell.messageBubbleImageView.image = [bubbleImageDataSource messageBubbleImage]; cell.messageBubbleImageView.highlightedImage = [bubbleImageDataSource messageBubbleHighlightedImage]; } else { id messageMedia = [messageItem media]; cell.mediaView = [messageMedia mediaView] ?: [messageMedia mediaPlaceholderView]; NSParameterAssert(cell.mediaView != nil); } BOOL needsAvatar = YES; if (isOutgoingMessage && CGSizeEqualToSize(collectionView.collectionViewLayout.outgoingAvatarViewSize, CGSizeZero)) { needsAvatar = NO; } else if (!isOutgoingMessage && CGSizeEqualToSize(collectionView.collectionViewLayout.incomingAvatarViewSize, CGSizeZero)) { needsAvatar = NO; } id 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]; cell.avatarImageView.highlightedImage = nil; } else { cell.avatarImageView.image = avatarImage; cell.avatarImageView.highlightedImage = [avatarImageDataSource avatarHighlightedImage]; } } } 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; [self collectionView:collectionView accessibilityForCell:cell indexPath:indexPath message:messageItem]; return cell; } - (void)collectionView:(JSQMessagesCollectionView *)collectionView accessibilityForCell:(JSQMessagesCollectionViewCell*)cell indexPath:(NSIndexPath *)indexPath message:(id)messageItem { const BOOL isMediaMessage = [messageItem isMediaMessage]; cell.isAccessibilityElement = YES; if (!isMediaMessage) { cell.accessibilityLabel = [NSString stringWithFormat:[NSBundle jsq_localizedStringForKey:@"text_message_accessibility_label"], [messageItem senderDisplayName], [messageItem text]]; } else { cell.accessibilityLabel = [NSString stringWithFormat:[NSBundle jsq_localizedStringForKey:@"media_message_accessibility_label"], [messageItem senderDisplayName]]; } } - (UICollectionReusableView *)collectionView:(JSQMessagesCollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { if (self.showTypingIndicator && [kind isEqualToString:UICollectionElementKindSectionFooter]) { return [collectionView dequeueTypingIndicatorFooterViewForIndexPath:indexPath]; } else if (self.showLoadEarlierMessagesHeader && [kind isEqualToString:UICollectionElementKindSectionHeader]) { return [collectionView dequeueLoadEarlierMessagesViewHeaderForIndexPath:indexPath]; } return nil; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { if (!self.showTypingIndicator) { return CGSizeZero; } return CGSizeMake([collectionViewLayout itemWidth], kJSQMessagesTypingIndicatorFooterViewHeight); } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { if (!self.showLoadEarlierMessagesHeader) { return CGSizeZero; } return CGSizeMake([collectionViewLayout itemWidth], kJSQMessagesLoadEarlierHeaderViewHeight); } #pragma mark - Collection view delegate - (BOOL)collectionView:(JSQMessagesCollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath { // disable menu for media messages id messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath]; if ([messageItem isMediaMessage]) { if ([[messageItem media] respondsToSelector:@selector(mediaDataType)]) { return YES; } 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; // it will reset the font and fontcolor when selectable is NO // however, the actual font and fontcolor in textView do not get changed // in order to preserve link colors, we need to re-assign the font and fontcolor when selectable is NO // see GitHub issues #1675 and #1759 selectedCell.textView.textColor = selectedCell.textView.textColor; selectedCell.textView.font = selectedCell.textView.font; return YES; } - (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender { if (action == @selector(copy:) || action == @selector(delete:)) { return YES; } return NO; } - (void)collectionView:(JSQMessagesCollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender { if (action == @selector(copy:)) { id messageData = [self collectionView:collectionView messageDataForItemAtIndexPath:indexPath]; if ([messageData isMediaMessage]) { id mediaData = [messageData media]; if ([messageData conformsToProtocol:@protocol(JSQMessageData)]) { [[UIPasteboard generalPasteboard] setValue:[mediaData mediaData] forPasteboardType:[mediaData mediaDataType]]; } } else { [[UIPasteboard generalPasteboard] setString:[messageData text]]; } } else if (action == @selector(delete:)) { [collectionView.dataSource collectionView:collectionView didDeleteMessageAtIndexPath:indexPath]; [collectionView deleteItemsAtIndexPaths:@[indexPath]]; [collectionView.collectionViewLayout invalidateLayout]; } } #pragma mark - Collection view delegate flow layout - (CGSize)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return [collectionViewLayout sizeForItemAtIndexPath:indexPath]; } - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath { return 0.0f; } - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath { return 0.0f; } - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath { return 0.0f; } - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapAvatarImageView:(UIImageView *)avatarImageView atIndexPath:(NSIndexPath *)indexPath { } - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath { } - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCellAtIndexPath:(NSIndexPath *)indexPath touchLocation:(CGPoint)touchLocation { } #pragma mark - Input toolbar delegate - (void)messagesInputToolbar:(JSQMessagesInputToolbar *)toolbar didPressLeftBarButton:(UIButton *)sender { if (toolbar.sendButtonLocation == JSQMessagesInputSendButtonLocationLeft) { [self didPressSendButton:sender withMessageText:[self jsq_currentlyComposedMessageText] senderId:[self.collectionView.dataSource senderId] senderDisplayName:[self.collectionView.dataSource senderDisplayName] date:[NSDate date]]; } else { [self didPressAccessoryButton:sender]; } } - (void)messagesInputToolbar:(JSQMessagesInputToolbar *)toolbar didPressRightBarButton:(UIButton *)sender { if (toolbar.sendButtonLocation == JSQMessagesInputSendButtonLocationRight) { [self didPressSendButton:sender withMessageText:[self jsq_currentlyComposedMessageText] senderId:[self.collectionView.dataSource senderId] senderDisplayName:[self.collectionView.dataSource senderDisplayName] date:[NSDate date]]; } else { [self didPressAccessoryButton:sender]; } } - (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]; return [self.inputToolbar.contentView.textView.text jsq_stringByTrimingWhitespace]; } #pragma mark - Input - (UIView *)inputAccessoryView { return self.inputToolbar; } - (BOOL)canBecomeFirstResponder { return YES; } #pragma mark - Text view delegate - (void)textViewDidBeginEditing:(UITextView *)textView { if (textView != self.inputToolbar.contentView.textView) { return; } [textView becomeFirstResponder]; if (self.automaticallyScrollsToMostRecentMessage) { [self scrollToBottomAnimated:YES]; } } - (void)textViewDidChange:(UITextView *)textView { if (textView != self.inputToolbar.contentView.textView) { return; } } - (void)textViewDidEndEditing:(UITextView *)textView { if (textView != self.inputToolbar.contentView.textView) { return; } [textView resignFirstResponder]; } #pragma mark - Notifications - (void)didReceiveMenuWillShowNotification:(NSNotification *)notification { 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(didReceiveMenuWillShowNotification:) name:UIMenuControllerWillShowMenuNotification object:nil]; } - (void)didReceiveMenuWillHideNotification:(NSNotification *)notification { 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]; selectedCell.textView.selectable = YES; self.selectedIndexPathForMenu = nil; } - (void)preferredContentSizeChanged:(NSNotification *)notification { [self.collectionView.collectionViewLayout invalidateLayout]; [self.collectionView setNeedsLayout]; } #pragma mark - Collection view utilities - (void)jsq_updateCollectionViewInsets { const CGFloat top = self.additionalContentInset.top; const CGFloat bottom = CGRectGetMaxY(self.collectionView.frame) - CGRectGetMinY(self.inputToolbar.frame) + self.additionalContentInset.bottom; [self jsq_setCollectionViewInsetsTopValue:top bottomValue:bottom]; } - (void)jsq_setCollectionViewInsetsTopValue:(CGFloat)top bottomValue:(CGFloat)bottom { UIEdgeInsets insets = UIEdgeInsetsMake(self.topLayoutGuide.length + top, 0.0f, bottom, 0.0f); self.collectionView.contentInset = insets; self.collectionView.scrollIndicatorInsets = insets; } - (BOOL)jsq_isMenuVisible { // check if cell copy menu is showing // it is only our menu if `selectedIndexPathForMenu` is not `nil` return self.selectedIndexPathForMenu != nil && [[UIMenuController sharedMenuController] isMenuVisible]; } #pragma mark - Utilities - (void)jsq_registerForNotifications:(BOOL)registerForNotifications { NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; if (registerForNotifications) { [center addObserver:self selector:@selector(jsq_didReceiveKeyboardWillChangeFrameNotification:) name:UIKeyboardWillChangeFrameNotification object:nil]; [center addObserver:self selector:@selector(didReceiveMenuWillShowNotification:) name:UIMenuControllerWillShowMenuNotification object:nil]; [center addObserver:self selector:@selector(didReceiveMenuWillHideNotification:) name:UIMenuControllerWillHideMenuNotification object:nil]; [center addObserver:self selector:@selector(preferredContentSizeChanged:) name:UIContentSizeCategoryDidChangeNotification object:nil]; } else { [center removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil]; [center removeObserver:self name:UIMenuControllerWillShowMenuNotification object:nil]; [center removeObserver:self name:UIMenuControllerWillHideMenuNotification object:nil]; [center removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil]; } } - (void)jsq_didReceiveKeyboardWillChangeFrameNotification:(NSNotification *)notification { 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]; [UIView animateWithDuration:animationDuration delay:0.0 options:animationCurveOption animations:^{ const UIEdgeInsets insets = self.additionalContentInset; [self jsq_setCollectionViewInsetsTopValue:insets.top bottomValue:CGRectGetHeight(keyboardEndFrame) + insets.bottom]; } completion:nil]; } @end ================================================ FILE: JSQMessagesViewController/Controllers/JSQMessagesViewController.xib ================================================ ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.h ================================================ // // 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 #import #import "JSQMessagesAvatarImage.h" NS_ASSUME_NONNULL_BEGIN /** * `JSQMessagesAvatarImageFactory` is a factory that provides a means for creating and styling * `JSQMessagesAvatarImage` objects to be displayed in a `JSQMessagesCollectionViewCell` of a `JSQMessagesCollectionView`. */ @interface JSQMessagesAvatarImageFactory : NSObject /** * Creates and returns a new instance of `JSQMessagesAvatarImageFactory` that uses * the default diameter for creating avatars. * * @return An initialized `JSQMessagesAvatarImageFactory` object. */ - (instancetype)init; /** * Creates and returns a new instance of `JSQMessagesAvatarImageFactory` that uses * the specified diameter for creating avatars. * * @param diameter An integer value specifying the diameter size of the image in points. This value must be greater than `0`. * * @return An initialized `JSQMessagesAvatarImageFactory` object. */ - (instancetype)initWithDiameter:(NSUInteger)diameter NS_DESIGNATED_INITIALIZER; /** * Creates and returns a `JSQMessagesAvatarImage` object with the specified placeholderImage that is * cropped to a circle of the given diameter. * * @param placeholderImage An image object that represents a placeholder avatar image. This value must not be `nil`. * * @return An initialized `JSQMessagesAvatarImage` object. */ - (JSQMessagesAvatarImage *)avatarImageWithPlaceholder:(UIImage *)placeholderImage; /** * Creates and returns a `JSQMessagesAvatarImage` object with the specified image that is * cropped to a circle of the given diameter and used for the `avatarImage` and `avatarPlaceholderImage` properties * of the returned `JSQMessagesAvatarImage` object. This image is then copied and has a transparent black mask applied to it, * which is used for the `avatarHighlightedImage` property of the returned `JSQMessagesAvatarImage` object. * * @param image An image object that represents an avatar image. This value must not be `nil`. * * @return An initialized `JSQMessagesAvatarImage` object. */ - (JSQMessagesAvatarImage *)avatarImageWithImage:(UIImage *)image; /** * Returns a copy of the specified image that is cropped to a circle with the given diameter. * * @param image The image to crop. This value must not be `nil`. * * @return A new image object. */ - (UIImage *)circularAvatarImage:(UIImage *)image; /** * Returns a copy of the specified image that is cropped to a circle with the given diameter. * Additionally, a transparent overlay is applied to the image to represent a pressed or highlighted state. * * @param image The image to crop. This value must not be `nil`. * * @return A new image object. */ - (UIImage *)circularAvatarHighlightedImage:(UIImage *)image; /** * Creates and returns a `JSQMessagesAvatarImage` object with a circular shape that displays the specified userInitials * with the given backgroundColor, textColor, font, and diameter. * * @param userInitials The user initials to display in the avatar image. This value must not be `nil`. * @param backgroundColor The background color of the avatar. This value must not be `nil`. * @param textColor The color of the text of the userInitials. This value must not be `nil`. * @param font The font applied to userInitials. This value must not be `nil`. * * @return An initialized `JSQMessagesAvatarImage` object. * * @discussion This method does not attempt to detect or correct incompatible parameters. * That is to say, you are responsible for providing a font size and diameter that make sense. * For example, a font size of `14.0f` and a diameter of `34.0f` will result in an avatar similar to Messages in iOS 7. * However, a font size `30.0f` and diameter of `10.0f` will not produce a desirable image. * Further, this method does not check the length of userInitials. It is recommended that you pass a string of length `2` or `3`. */ - (JSQMessagesAvatarImage *)avatarImageWithUserInitials:(NSString *)userInitials backgroundColor:(UIColor *)backgroundColor textColor:(UIColor *)textColor font:(UIFont *)font; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.m ================================================ // // 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 "JSQMessagesAvatarImageFactory.h" #import "UIColor+JSQMessages.h" // TODO: define kJSQMessagesCollectionViewAvatarSizeDefault elsewhere so we can remove this import #import "JSQMessagesCollectionViewFlowLayout.h" @interface JSQMessagesAvatarImageFactory () @property (assign, nonatomic, readonly) NSUInteger diameter; @end @implementation JSQMessagesAvatarImageFactory #pragma mark - Initialization - (instancetype)init { return [self initWithDiameter:kJSQMessagesCollectionViewAvatarSizeDefault]; } - (instancetype)initWithDiameter:(NSUInteger)diameter { NSParameterAssert(diameter > 0); self = [super init]; if (self) { _diameter = diameter; } return self; } #pragma mark - Public - (JSQMessagesAvatarImage *)avatarImageWithPlaceholder:(UIImage *)placeholderImage { UIImage *circlePlaceholderImage = [self jsq_circularImage:placeholderImage withHighlightedColor:nil]; return [JSQMessagesAvatarImage avatarImageWithPlaceholder:circlePlaceholderImage]; } - (JSQMessagesAvatarImage *)avatarImageWithImage:(UIImage *)image { UIImage *avatar = [self circularAvatarImage:image]; UIImage *highlightedAvatar = [self circularAvatarHighlightedImage:image]; return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatar highlightedImage:highlightedAvatar placeholderImage:avatar]; } - (UIImage *)circularAvatarImage:(UIImage *)image { return [self jsq_circularImage:image withHighlightedColor:nil]; } - (UIImage *)circularAvatarHighlightedImage:(UIImage *)image { return [self jsq_circularImage:image withHighlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]]; } - (JSQMessagesAvatarImage *)avatarImageWithUserInitials:(NSString *)userInitials backgroundColor:(UIColor *)backgroundColor textColor:(UIColor *)textColor font:(UIFont *)font { UIImage *avatarImage = [self jsq_imageWithInitials:userInitials backgroundColor:backgroundColor textColor:textColor font:font]; UIImage *avatarHighlightedImage = [self jsq_circularImage:avatarImage withHighlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]]; return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatarImage highlightedImage:avatarHighlightedImage placeholderImage:avatarImage]; } #pragma mark - Private - (UIImage *)jsq_imageWithInitials:(NSString *)initials backgroundColor:(UIColor *)backgroundColor textColor:(UIColor *)textColor font:(UIFont *)font { NSParameterAssert(initials != nil); NSParameterAssert(backgroundColor != nil); NSParameterAssert(textColor != nil); NSParameterAssert(font != nil); CGRect frame = CGRectMake(0.0f, 0.0f, self.diameter, self.diameter); NSDictionary *attributes = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor }; CGRect textFrame = [initials boundingRectWithSize:frame.size options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) attributes:attributes context:nil]; CGPoint frameMidPoint = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)); CGPoint textFrameMidPoint = CGPointMake(CGRectGetMidX(textFrame), CGRectGetMidY(textFrame)); CGFloat dx = frameMidPoint.x - textFrameMidPoint.x; CGFloat dy = frameMidPoint.y - textFrameMidPoint.y; CGPoint drawPoint = CGPointMake(dx, dy); UIImage *image = nil; UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale); { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, backgroundColor.CGColor); CGContextFillRect(context, frame); [initials drawAtPoint:drawPoint withAttributes:attributes]; image = UIGraphicsGetImageFromCurrentImageContext(); } UIGraphicsEndImageContext(); return [self jsq_circularImage:image withHighlightedColor:nil]; } - (UIImage *)jsq_circularImage:(UIImage *)image withHighlightedColor:(UIColor *)highlightedColor { NSParameterAssert(image != nil); CGRect frame = CGRectMake(0.0f, 0.0f, self.diameter, self.diameter); UIImage *newImage = nil; UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale); { CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *imgPath = [UIBezierPath bezierPathWithOvalInRect:frame]; [imgPath addClip]; [image drawInRect:frame]; if (highlightedColor != nil) { CGContextSetFillColorWithColor(context, highlightedColor.CGColor); CGContextFillEllipseInRect(context, frame); } newImage = UIGraphicsGetImageFromCurrentImageContext(); } UIGraphicsEndImageContext(); return newImage; } @end ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.h ================================================ // // 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 #import #import "JSQMessagesBubbleImage.h" NS_ASSUME_NONNULL_BEGIN /** * `JSQMessagesBubbleImageFactory` is a factory that provides a means for creating and styling * `JSQMessagesBubbleImage` objects to be displayed in a `JSQMessagesCollectionViewCell` of a `JSQMessagesCollectionView`. */ @interface JSQMessagesBubbleImageFactory : NSObject /** * Specifies the layout direction of the message bubble. The default value is initialized * from `[UIApplication sharedApplication]`. */ @property (nonatomic, assign) UIUserInterfaceLayoutDirection layoutDirection; /** * Creates and returns a new instance of `JSQMessagesBubbleImageFactory` that uses the * default bubble image assets, cap insets, and layout direction. * * @return An initialized `JSQMessagesBubbleImageFactory` object. */ - (instancetype)init; /** * Creates and returns a new instance of `JSQMessagesBubbleImageFactory` having the specified * bubbleImage and capInsets. These values are used internally in the factory to produce * `JSQMessagesBubbleImage` objects. * * @param bubbleImage A template bubble image from which all images will be generated. * The image should represent the *outgoing* message bubble image, which will be flipped * horizontally for generating the corresponding *incoming* message bubble images. This value must not be `nil`. * * @param capInsets The values to use for the cap insets that define the unstretchable regions of the image. * Specify `UIEdgeInsetsZero` to have the factory create insets that allow the image to stretch from its center point. * * @return An initialized `JSQMessagesBubbleImageFactory`. */ - (instancetype)initWithBubbleImage:(UIImage *)bubbleImage capInsets:(UIEdgeInsets)capInsets layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection; /** * Creates and returns a `JSQMessagesBubbleImage` object with the specified color for *outgoing* message image bubbles. * The `messageBubbleImage` property of the `JSQMessagesBubbleImage` is configured with a flat bubble image, masked to the given color. * The `messageBubbleHighlightedImage` property is configured similarly, but with a darkened version of the given color. * * @param color The color of the bubble image in the image view. This value must not be `nil`. * * @return An initialized `JSQMessagesBubbleImage` object. */ - (JSQMessagesBubbleImage *)outgoingMessagesBubbleImageWithColor:(UIColor *)color; /** * Creates and returns a `JSQMessagesBubbleImage` object with the specified color for *incoming* message image bubbles. * The `messageBubbleImage` property of the `JSQMessagesBubbleImage` is configured with a flat bubble image, masked to the given color. * The `messageBubbleHighlightedImage` property is configured similarly, but with a darkened version of the given color. * * @param color The color of the bubble image in the image view. This value must not be `nil`. * * @return An initialized `JSQMessagesBubbleImage` object. */ - (JSQMessagesBubbleImage *)incomingMessagesBubbleImageWithColor:(UIColor *)color; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.m ================================================ // // 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 "JSQMessagesBubbleImageFactory.h" #import "UIImage+JSQMessages.h" #import "UIColor+JSQMessages.h" @interface JSQMessagesBubbleImageFactory () @property (strong, nonatomic, readonly) UIImage *bubbleImage; @property (assign, nonatomic, readonly) UIEdgeInsets capInsets; @property (assign, nonatomic, readonly) BOOL isRightToLeftLanguage; @end @implementation JSQMessagesBubbleImageFactory #pragma mark - Initialization - (instancetype)initWithBubbleImage:(UIImage *)bubbleImage capInsets:(UIEdgeInsets)capInsets layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection { NSParameterAssert(bubbleImage != nil); self = [super init]; if (self) { _bubbleImage = bubbleImage; _capInsets = capInsets; _layoutDirection = layoutDirection; if (UIEdgeInsetsEqualToEdgeInsets(capInsets, UIEdgeInsetsZero)) { _capInsets = [self jsq_centerPointEdgeInsetsForImageSize:bubbleImage.size]; } } return self; } - (instancetype)init { return [self initWithBubbleImage:[UIImage jsq_bubbleCompactImage] capInsets:UIEdgeInsetsZero layoutDirection:[UIApplication sharedApplication].userInterfaceLayoutDirection]; } #pragma mark - Public - (JSQMessagesBubbleImage *)outgoingMessagesBubbleImageWithColor:(UIColor *)color { return [self jsq_messagesBubbleImageWithColor:color flippedForIncoming:NO ^ self.isRightToLeftLanguage]; } - (JSQMessagesBubbleImage *)incomingMessagesBubbleImageWithColor:(UIColor *)color { return [self jsq_messagesBubbleImageWithColor:color flippedForIncoming:YES ^ self.isRightToLeftLanguage]; } #pragma mark - Private - (BOOL)isRightToLeftLanguage { return (self.layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft); } - (UIEdgeInsets)jsq_centerPointEdgeInsetsForImageSize:(CGSize)bubbleImageSize { // make image stretchable from center point CGPoint center = CGPointMake(bubbleImageSize.width / 2.0f, bubbleImageSize.height / 2.0f); return UIEdgeInsetsMake(center.y, center.x, center.y, center.x); } - (JSQMessagesBubbleImage *)jsq_messagesBubbleImageWithColor:(UIColor *)color flippedForIncoming:(BOOL)flippedForIncoming { NSParameterAssert(color != nil); UIImage *normalBubble = [self.bubbleImage jsq_imageMaskedWithColor:color]; UIImage *highlightedBubble = [self.bubbleImage jsq_imageMaskedWithColor:[color jsq_colorByDarkeningColorWithValue:0.12f]]; if (flippedForIncoming) { normalBubble = [self jsq_horizontallyFlippedImageFromImage:normalBubble]; highlightedBubble = [self jsq_horizontallyFlippedImageFromImage:highlightedBubble]; } normalBubble = [self jsq_stretchableImageFromImage:normalBubble withCapInsets:self.capInsets]; highlightedBubble = [self jsq_stretchableImageFromImage:highlightedBubble withCapInsets:self.capInsets]; return [[JSQMessagesBubbleImage alloc] initWithMessageBubbleImage:normalBubble highlightedImage:highlightedBubble]; } - (UIImage *)jsq_horizontallyFlippedImageFromImage:(UIImage *)image { return [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:UIImageOrientationUpMirrored]; } - (UIImage *)jsq_stretchableImageFromImage:(UIImage *)image withCapInsets:(UIEdgeInsets)capInsets { return [image resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; } @end ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.h ================================================ // // 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 #import @class JSQMessagesBubbleImageFactory; NS_ASSUME_NONNULL_BEGIN /** * An instance of `JSQMessagesMediaViewBubbleImageMasker` is an object that masks * media views for a `JSQMessageMediaData` object. Given a view, it will mask the view * with a bubble image for an outgoing or incoming media view. * * @see JSQMessageMediaData. * @see JSQMessagesBubbleImageFactory. * @see JSQMessagesBubbleImage. */ @interface JSQMessagesMediaViewBubbleImageMasker : NSObject /** * Returns the bubble image factory that the masker uses to mask media views. */ @property (strong, nonatomic, readonly) JSQMessagesBubbleImageFactory *bubbleImageFactory; /** * Creates and returns a new instance of `JSQMessagesMediaViewBubbleImageMasker` * that uses a default instance of `JSQMessagesBubbleImageFactory`. The masker uses the `JSQMessagesBubbleImage` * objects returned by the factory to mask media views. * * @return An initialized `JSQMessagesMediaViewBubbleImageMasker` object. * * @see JSQMessagesBubbleImageFactory. * @see JSQMessagesBubbleImage. */ - (instancetype)init; /** * Creates and returns a new instance of `JSQMessagesMediaViewBubbleImageMasker` * having the specified bubbleImageFactory. The masker uses the `JSQMessagesBubbleImage` * objects returned by the factory to mask media views. * * @param bubbleImageFactory An initialized `JSQMessagesBubbleImageFactory` object to use for masking media views. This value must not be `nil`. * * @return An initialized `JSQMessagesMediaViewBubbleImageMasker` object. * * @see JSQMessagesBubbleImageFactory. * @see JSQMessagesBubbleImage. */ - (instancetype)initWithBubbleImageFactory:(JSQMessagesBubbleImageFactory *)bubbleImageFactory NS_DESIGNATED_INITIALIZER; /** * Applies an outgoing bubble image mask to the specified mediaView. * * @param mediaView The media view to mask. */ - (void)applyOutgoingBubbleImageMaskToMediaView:(UIView *)mediaView; /** * Applies an incoming bubble image mask to the specified mediaView. * * @param mediaView The media view to mask. */ - (void)applyIncomingBubbleImageMaskToMediaView:(UIView *)mediaView; /** * A convenience method for applying a bubble image mask to the specified mediaView. * This method uses the default instance of `JSQMessagesBubbleImageFactory`. * * @param mediaView The media view to mask. * @param isOutgoing A boolean value specifiying whether or not the mask should be for an outgoing or incoming view. * Specify `YES` for outgoing and `NO` for incoming. */ + (void)applyBubbleImageMaskToMediaView:(UIView *)mediaView isOutgoing:(BOOL)isOutgoing; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.m ================================================ // // 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 "JSQMessagesMediaViewBubbleImageMasker.h" #import "JSQMessagesBubbleImageFactory.h" @implementation JSQMessagesMediaViewBubbleImageMasker #pragma mark - Initialization - (instancetype)init { return [self initWithBubbleImageFactory:[[JSQMessagesBubbleImageFactory alloc] init]]; } - (instancetype)initWithBubbleImageFactory:(JSQMessagesBubbleImageFactory *)bubbleImageFactory { NSParameterAssert(bubbleImageFactory != nil); self = [super init]; if (self) { _bubbleImageFactory = bubbleImageFactory; } return self; } #pragma mark - View masking - (void)applyOutgoingBubbleImageMaskToMediaView:(UIView *)mediaView { JSQMessagesBubbleImage *bubbleImageData = [self.bubbleImageFactory outgoingMessagesBubbleImageWithColor:[UIColor whiteColor]]; [self jsq_maskView:mediaView withImage:[bubbleImageData messageBubbleImage]]; } - (void)applyIncomingBubbleImageMaskToMediaView:(UIView *)mediaView { JSQMessagesBubbleImage *bubbleImageData = [self.bubbleImageFactory incomingMessagesBubbleImageWithColor:[UIColor whiteColor]]; [self jsq_maskView:mediaView withImage:[bubbleImageData messageBubbleImage]]; } + (void)applyBubbleImageMaskToMediaView:(UIView *)mediaView isOutgoing:(BOOL)isOutgoing { JSQMessagesMediaViewBubbleImageMasker *masker = [[JSQMessagesMediaViewBubbleImageMasker alloc] init]; if (isOutgoing) { [masker applyOutgoingBubbleImageMaskToMediaView:mediaView]; } else { [masker applyIncomingBubbleImageMaskToMediaView:mediaView]; } } #pragma mark - Private - (void)jsq_maskView:(UIView *)view withImage:(UIImage *)image { NSParameterAssert(view != nil); NSParameterAssert(image != nil); UIImageView *imageViewMask = [[UIImageView alloc] initWithImage:image]; imageViewMask.frame = CGRectInset(view.frame, 2.0f, 2.0f); view.layer.mask = imageViewMask.layer; } @end ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.h ================================================ // // 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 #import NS_ASSUME_NONNULL_BEGIN /** * An instance of `JSQMessagesTimestampFormatter` is a singleton object that provides an efficient means * for creating attributed and non-attributed string representations of `NSDate` objects. * It is intended to be used as the method by which you display timestamps in a `JSQMessagesCollectionView`. */ @interface JSQMessagesTimestampFormatter : NSObject /** * Returns the cached date formatter object used by the `JSQMessagesTimestampFormatter` shared instance. */ @property (strong, nonatomic, readonly) NSDateFormatter *dateFormatter; /** * The text attributes to apply to the day, month, and year components of the string representation of a given date. * The default value is a dictionary containing attributes that specify centered, light gray text and `UIFontTextStyleBody` font. */ @property (copy, nonatomic) NSDictionary *dateTextAttributes; /** * The text attributes to apply to the minute and hour componenents of the string representation of a given date. * The default value is a dictionary containing attributes that specify centered, light gray text and `UIFontTextStyleBody` font. */ @property (copy, nonatomic) NSDictionary *timeTextAttributes; /** * Returns the shared timestamp formatter object. * * @return The shared timestamp formatter object. */ + (JSQMessagesTimestampFormatter *)sharedFormatter; /** * Returns a string representation of the given date formatted in the current locale using `NSDateFormatterMediumStyle` for the date style * and `NSDateFormatterShortStyle` for the time style. It uses relative date formatting where possible. * * @param date The date to format. * * @return A formatted string representation of date. */ - (NSString *)timestampForDate:(NSDate *)date; /** * Returns an attributed string representation of the given date formatted as described in `timestampForDate:`. * It applies the attributes in `dateTextAttributes` and `timeTextAttributes`, respectively. * * @param date The date to format. * * @return A formatted, attributed string representation of date. * * @see `timestampForDate:`. * @see `dateTextAttributes`. * @see `timeTextAttributes`. */ - (NSAttributedString *)attributedTimestampForDate:(NSDate *)date; /** * Returns a string representation of *only* the minute and hour components of the given date formatted in the current locale styled using `NSDateFormatterShortStyle`. * * @param date The date to format. * * @return A formatted string representation of the minute and hour components of date. */ - (NSString *)timeForDate:(NSDate *)date; /** * Returns a string representation of *only* the day, month, and year components of the given date formatted in the current locale styled using `NSDateFormatterMediumStyle`. * * @param date The date to format. * * @return A formatted string representation of the day, month, and year components of date. */ - (NSString *)relativeDateForDate:(NSDate *)date; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.m ================================================ // // 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 "JSQMessagesTimestampFormatter.h" @interface JSQMessagesTimestampFormatter () @property (strong, nonatomic, readwrite) NSDateFormatter *dateFormatter; @end @implementation JSQMessagesTimestampFormatter #pragma mark - Initialization + (JSQMessagesTimestampFormatter *)sharedFormatter { static JSQMessagesTimestampFormatter *_sharedFormatter = nil; static dispatch_once_t onceToken = 0; dispatch_once(&onceToken, ^{ _sharedFormatter = [[JSQMessagesTimestampFormatter alloc] init]; }); return _sharedFormatter; } - (instancetype)init { self = [super init]; if (self) { _dateFormatter = [[NSDateFormatter alloc] init]; [_dateFormatter setLocale:[NSLocale currentLocale]]; [_dateFormatter setDoesRelativeDateFormatting:YES]; UIColor *color = [UIColor lightGrayColor]; NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; paragraphStyle.alignment = NSTextAlignmentCenter; _dateTextAttributes = @{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : color, NSParagraphStyleAttributeName : paragraphStyle }; _timeTextAttributes = @{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : color, NSParagraphStyleAttributeName : paragraphStyle }; } return self; } #pragma mark - Formatter - (NSString *)timestampForDate:(NSDate *)date { if (!date) { return nil; } [self.dateFormatter setDateStyle:NSDateFormatterMediumStyle]; [self.dateFormatter setTimeStyle:NSDateFormatterShortStyle]; return [self.dateFormatter stringFromDate:date]; } - (NSAttributedString *)attributedTimestampForDate:(NSDate *)date { if (!date) { return nil; } NSString *relativeDate = [self relativeDateForDate:date]; NSString *time = [self timeForDate:date]; NSMutableAttributedString *timestamp = [[NSMutableAttributedString alloc] initWithString:relativeDate attributes:self.dateTextAttributes]; [timestamp appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; [timestamp appendAttributedString:[[NSAttributedString alloc] initWithString:time attributes:self.timeTextAttributes]]; return [[NSAttributedString alloc] initWithAttributedString:timestamp]; } - (NSString *)timeForDate:(NSDate *)date { if (!date) { return nil; } [self.dateFormatter setDateStyle:NSDateFormatterNoStyle]; [self.dateFormatter setTimeStyle:NSDateFormatterShortStyle]; return [self.dateFormatter stringFromDate:date]; } - (NSString *)relativeDateForDate:(NSDate *)date { if (!date) { return nil; } [self.dateFormatter setDateStyle:NSDateFormatterMediumStyle]; [self.dateFormatter setTimeStyle:NSDateFormatterNoStyle]; return [self.dateFormatter stringFromDate:date]; } @end ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.h ================================================ // // 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 #import NS_ASSUME_NONNULL_BEGIN /** * `JSQMessagesToolbarButtonFactory` is a factory that provides a means for creating the default * toolbar button items to be displayed in the content view of a `JSQMessagesInputToolbar`. */ @interface JSQMessagesToolbarButtonFactory : NSObject /** * Creates and returns a new instance of `JSQMessagesToolbarButtonFactory` that uses * the default font for creating buttons. * * @return An initialized `JSQMessagesToolbarButtonFactory` object. */ - (instancetype)init; /** * Creates and returns a new instance of `JSQMessagesToolbarButtonFactory` that uses * the specified font for creating buttons. * * @param font The font that will be used for the buttons produced by the factory. * * @return An initialized `JSQMessagesToolbarButtonFactory` object. */ - (instancetype)initWithFont:(UIFont *)font NS_DESIGNATED_INITIALIZER; /** * Creates and returns a new button that is styled as the default accessory button. * The button has a paper clip icon image and no text. * * @return A newly created button. */ - (UIButton *)defaultAccessoryButtonItem; /** * Creates and returns a new button that is styled as the default send button. * The button has title text `@"Send"` and no image. * * @return A newly created button. */ - (UIButton *)defaultSendButtonItem; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.m ================================================ // // 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 "JSQMessagesToolbarButtonFactory.h" #import "UIColor+JSQMessages.h" #import "UIImage+JSQMessages.h" #import "NSBundle+JSQMessages.h" @interface JSQMessagesToolbarButtonFactory () @property (strong, nonatomic, readonly) UIFont *buttonFont; @end @implementation JSQMessagesToolbarButtonFactory - (instancetype)init { return [self initWithFont:[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]]; } - (instancetype)initWithFont:(UIFont *)font { NSParameterAssert(font != nil); self = [super init]; if (self) { _buttonFont = font; } return self; } - (UIButton *)defaultAccessoryButtonItem { UIImage *accessoryImage = [UIImage jsq_defaultAccessoryImage]; UIImage *normalImage = [accessoryImage jsq_imageMaskedWithColor:[UIColor lightGrayColor]]; 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.titleLabel.font = self.buttonFont; accessoryButton.accessibilityLabel = [NSBundle jsq_localizedStringForKey:@"accessory_button_accessibility_label"]; return accessoryButton; } - (UIButton *)defaultSendButtonItem { NSString *sendTitle = [NSBundle jsq_localizedStringForKey:@"send"]; UIButton *sendButton = [[UIButton alloc] initWithFrame:CGRectZero]; [sendButton setTitle:sendTitle forState:UIControlStateNormal]; [sendButton setTitleColor:[UIColor jsq_messageBubbleBlueColor] forState:UIControlStateNormal]; [sendButton setTitleColor:[[UIColor jsq_messageBubbleBlueColor] jsq_colorByDarkeningColorWithValue:0.1f] forState:UIControlStateHighlighted]; [sendButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; sendButton.titleLabel.font = self.buttonFont; sendButton.titleLabel.adjustsFontSizeToFitWidth = YES; sendButton.titleLabel.minimumScaleFactor = 0.85f; 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; } @end ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesVideoThumbnailFactory.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN /** * A completion block for a `JSQMessagesVideoThumbnailFactory`. * * @see `thumbnailWithVideoMediaAsset: completion:` */ typedef void (^JSQMessagesVideoThumbnailCompletionBlock)(UIImage * _Nullable, NSError * _Nullable); /** * `JSQMessagesVideoThumbnailFactory` is a factory that provides a means for generating * a thumbnail image with for a `JSQVideoMediaItem`. */ @interface JSQMessagesVideoThumbnailFactory : NSObject /** * Generates and returns a thumbnail image for the specified video media asset * with the default time of `CMTimeMakeWithSeconds(1, 2)`. * * The specified block is executed upon completion of generating the thumbnail image * and is executed on the main thread. * * @param asset The `AVURLAsset` for the video media item. * @param completion The block to call after the thumbnail has been generated. */ - (void)thumbnailWithVideoMediaAsset:(AVURLAsset *)asset completion:(JSQMessagesVideoThumbnailCompletionBlock)completion; /** * Generates and returns a thumbnail image for the specified video media asset with a given CMTime. * * The specified block is executed upon completion of generating the thumbnail image and is executed on the app’s main thread. * * @param videoMediaAsset The instance of AVURLAsset for the video media item. * @param time The CMTime struct for capturing the thumbnail image from the video media item. * @param completion The block to call after the thumbnail has been generated. */ /** * Generates and returns a thumbnail image for the specified video media asset with the given `CMTime`. * * The specified block is executed upon completion of generating the thumbnail image * and is executed on the main thread. * * @param asset The `AVURLAsset` for the video media item. * @param time The CMTime for capturing the thumbnail image from the video asset. * @param completion The block to call after the thumbnail has been generated. */ - (void)thumbnailWithVideoMediaAsset:(AVURLAsset *)asset time:(CMTime)time completion:(JSQMessagesVideoThumbnailCompletionBlock)completion; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Factories/JSQMessagesVideoThumbnailFactory.m ================================================ // // 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 "JSQVideoMediaItem.h" #import "JSQMessagesVideoThumbnailFactory.h" #import "JSQMessagesBubbleImageFactory.h" #import "UIImage+JSQMessages.h" @implementation JSQMessagesVideoThumbnailFactory - (void)thumbnailWithVideoMediaAsset:(AVURLAsset *)asset completion:(JSQMessagesVideoThumbnailCompletionBlock)completion { NSParameterAssert(asset != nil); NSParameterAssert(completion != nil); [self thumbnailWithVideoMediaAsset:asset time:CMTimeMakeWithSeconds(1, 2) completion:completion]; } - (void)thumbnailWithVideoMediaAsset:(AVURLAsset *)asset time:(CMTime)time completion:(JSQMessagesVideoThumbnailCompletionBlock)completion { NSParameterAssert(asset != nil); NSParameterAssert(completion != nil); AVAssetImageGenerator *generate = [[AVAssetImageGenerator alloc] initWithAsset:asset]; generate.appliesPreferredTrackTransform = YES; if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { generate.maximumSize = CGSizeMake(315.0f, 225.0f); } else { generate.maximumSize = CGSizeMake(210.0f, 150.0f); } NSArray *times = [NSArray arrayWithObject:[NSValue valueWithCMTime:time]]; [generate generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef im, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { UIImage *image = (result == AVAssetImageGeneratorSucceeded) ? [UIImage imageWithCGImage:im] : nil; if (completion) { completion(image, error); } }]; } @end ================================================ FILE: JSQMessagesViewController/JSQMessages.h ================================================ // // 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 // #ifndef JSQMessages_JSQMessages_h #define JSQMessages_JSQMessages_h #import "JSQMessagesViewController.h" // Views #import "JSQMessagesCollectionView.h" #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" #import "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesLoadEarlierHeaderView.h" // Layout #import "JSQMessagesBubbleSizeCalculating.h" #import "JSQMessagesBubblesSizeCalculator.h" #import "JSQMessagesCollectionViewFlowLayout.h" #import "JSQMessagesCollectionViewLayoutAttributes.h" #import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h" #import "JSQAudioMediaViewAttributes.h" // Toolbar #import "JSQMessagesComposerTextView.h" #import "JSQMessagesInputToolbar.h" #import "JSQMessagesToolbarContentView.h" // Model #import "JSQMessage.h" #import "JSQMediaItem.h" #import "JSQAudioMediaItem.h" #import "JSQPhotoMediaItem.h" #import "JSQLocationMediaItem.h" #import "JSQVideoMediaItem.h" #import "JSQMessagesBubbleImage.h" #import "JSQMessagesAvatarImage.h" #import "JSQAudioMediaViewAttributes.h" // Protocols #import "JSQMessageData.h" #import "JSQMessageMediaData.h" #import "JSQMessageAvatarImageDataSource.h" #import "JSQMessageBubbleImageDataSource.h" #import "JSQMessagesCollectionViewDataSource.h" #import "JSQMessagesCollectionViewDelegateFlowLayout.h" #import "JSQMessagesViewAccessoryButtonDelegate.h" // Factories #import "JSQMessagesAvatarImageFactory.h" #import "JSQMessagesBubbleImageFactory.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" #import "JSQMessagesTimestampFormatter.h" #import "JSQMessagesToolbarButtonFactory.h" // Categories #import "NSString+JSQMessages.h" #import "UIColor+JSQMessages.h" #import "UIImage+JSQMessages.h" #import "UIView+JSQMessages.h" #import "NSBundle+JSQMessages.h" #endif ================================================ FILE: JSQMessagesViewController/Layout/JSQAudioMediaViewAttributes.h ================================================ // // 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 #import #import NS_ASSUME_NONNULL_BEGIN /** An instance of `JSQAudioMediaViewAttributes` specifies the appearance configuration of a `JSQAudioMediaItem`. Use this class to customize the appearance of `JSQAudioMediaItem`. */ @interface JSQAudioMediaViewAttributes : NSObject /** * The image for the play button. The default is a play icon. */ @property (nonatomic, strong) UIImage *playButtonImage; /** * The image for the pause button. The default is a pause icon. */ @property (nonatomic, strong) UIImage *pauseButtonImage; /** * The font for the elapsed time label. The default is a system font. */ @property (strong, nonatomic) UIFont *labelFont; /** * Specifies whether to show fractions of a second for audio files with a duration of less than 1 minute. */ @property (nonatomic, assign) BOOL showFractionalSeconds; /** * The background color for the player. */ @property (nonatomic, strong) UIColor *backgroundColor; /** * The tint color for the player. */ @property (nonatomic, strong) UIColor *tintColor; /** * Insets that sepcify the padding around the play/pause button and time label. */ @property (nonatomic, assign) UIEdgeInsets controlInsets; /** * Specifies the padding between the button, progress bar, and label. */ @property (nonatomic, assign) CGFloat controlPadding; /** * Specifies the audio category set prior to playback. */ @property (nonatomic, copy) NSString *audioCategory; /** * Specifies the audio category options set prior to playback. */ @property (nonatomic) AVAudioSessionCategoryOptions audioCategoryOptions; /** Initializes and returns a `JSQAudioMediaViewAttributes` instance having the specified attributes. @param playButtonImage The image for the play button. @param pauseButtonImage The image for the pause button. @param labelFont The font for the elapsed time label. @param showFractionalSeconds Specifies whether to show fractions of a second for audio files with a duration of less than 1 minute. @param backgroundColor The background color for the player. @param tintColor The tint color for the player. @param controlInsets Insets that sepcify the padding around the play/pause button and time label. @param controlPadding Specifies the padding between the button, progress bar, and label. @param audioCategory Specifies the audio category set prior to playback. @param audioCategoryOptions Specifies the audio category options set prior to playback. @return A new `JSQAudioMediaViewAttributes` instance */ - (instancetype)initWithPlayButtonImage:(UIImage *)playButtonImage pauseButtonImage:(UIImage *)pauseButtonImage labelFont:(UIFont *)labelFont showFractionalSecodns:(BOOL)showFractionalSeconds backgroundColor:(UIColor *)backgroundColor tintColor:(UIColor *)tintColor controlInsets:(UIEdgeInsets)controlInsets controlPadding:(CGFloat)controlPadding audioCategory:(NSString *)audioCategory audioCategoryOptions:(AVAudioSessionCategoryOptions)audioCategoryOptions NS_DESIGNATED_INITIALIZER; /** Initializes and returns a default `JSQAudioMediaViewAttributes` instance. @return A new `JSQAudioMediaViewAttributes` instance */ - (instancetype)init; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Layout/JSQAudioMediaViewAttributes.m ================================================ // // 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 "JSQAudioMediaViewAttributes.h" #import "UIImage+JSQMessages.h" #import "UIColor+JSQMessages.h" @implementation JSQAudioMediaViewAttributes - (instancetype)initWithPlayButtonImage:(UIImage *)playButtonImage pauseButtonImage:(UIImage *)pauseButtonImage labelFont:(UIFont *)labelFont showFractionalSecodns:(BOOL)showFractionalSeconds backgroundColor:(UIColor *)backgroundColor tintColor:(UIColor *)tintColor controlInsets:(UIEdgeInsets)controlInsets controlPadding:(CGFloat)controlPadding audioCategory:(NSString *)audioCategory audioCategoryOptions:(AVAudioSessionCategoryOptions)audioCategoryOptions { NSParameterAssert(playButtonImage != nil); NSParameterAssert(pauseButtonImage != nil); NSParameterAssert(labelFont != nil); NSParameterAssert(backgroundColor != nil); NSParameterAssert(tintColor != nil); NSParameterAssert(audioCategory != nil); self = [super init]; if (self) { _playButtonImage = playButtonImage; _pauseButtonImage = pauseButtonImage; _labelFont = labelFont; _showFractionalSeconds = showFractionalSeconds; _backgroundColor = backgroundColor; _tintColor = tintColor; _controlInsets = controlInsets; _controlPadding = controlPadding; _audioCategory = audioCategory; _audioCategoryOptions = audioCategoryOptions; } return self; } - (instancetype)init { UIColor *tintColor = [UIColor jsq_messageBubbleBlueColor]; AVAudioSessionCategoryOptions options = AVAudioSessionCategoryOptionDuckOthers | AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth; return [self initWithPlayButtonImage:[[UIImage jsq_defaultPlayImage] jsq_imageMaskedWithColor:tintColor] pauseButtonImage:[[UIImage jsq_defaultPauseImage] jsq_imageMaskedWithColor:tintColor] labelFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody] showFractionalSecodns:NO backgroundColor:[UIColor jsq_messageBubbleLightGrayColor] tintColor:tintColor controlInsets:UIEdgeInsetsMake(6, 6, 6, 18) controlPadding:6 audioCategory:@"AVAudioSessionCategoryPlayback" audioCategoryOptions:options]; } - (void)setPlayButtonImage:(UIImage *)playButtonImage { NSParameterAssert(playButtonImage != nil); _playButtonImage = playButtonImage; } - (void)setPauseButtonImage:(UIImage *)pauseButtonImage { NSParameterAssert(pauseButtonImage != nil); _pauseButtonImage = pauseButtonImage; } - (void)setLabelFont:(UIFont *)labelFont { NSParameterAssert(labelFont != nil); _labelFont = labelFont; } - (void)setBackgroundColor:(UIColor *)backgroundColor { NSParameterAssert(backgroundColor != nil); _backgroundColor = backgroundColor; } - (void)setTintColor:(UIColor *)tintColor { NSParameterAssert(tintColor != nil); _tintColor = tintColor; } - (void)setAudioCategory:(NSString *)audioCategory { NSParameterAssert(audioCategory != nil); _audioCategory = audioCategory; } @end ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesBubbleSizeCalculating.h ================================================ // // 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 #import @class JSQMessagesCollectionViewFlowLayout; @protocol JSQMessageData; NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesBubbleSizeCalculating` protocol defines the common interface through which * an object provides layout information to an instance of `JSQMessagesCollectionViewFlowLayout`. * * A concrete class that conforms to this protocol is provided in the library. * See `JSQMessagesBubbleSizeCalculator`. */ @protocol JSQMessagesBubbleSizeCalculating /** * Computes and returns the size of the `messageBubbleImageView` property * of a `JSQMessagesCollectionViewCell` for the specified messageData at indexPath. * * @param messageData A message data object. * @param indexPath The index path at which messageData is located. * @param layout The layout object asking for this information. * * @return A sizes that specifies the required dimensions to display the entire message contents. * Note, this is *not* the entire cell, but only its message bubble. */ - (CGSize)messageBubbleSizeForMessageData:(id)messageData atIndexPath:(NSIndexPath *)indexPath withLayout:(JSQMessagesCollectionViewFlowLayout *)layout; /** * Notifies the receiver that the layout will be reset. * Use this method to clear any cached layout information, if necessary. * * @param layout The layout object notifying the receiver. */ - (void)prepareForResettingLayout:(JSQMessagesCollectionViewFlowLayout *)layout; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.h ================================================ // // 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 #import "JSQMessagesBubbleSizeCalculating.h" NS_ASSUME_NONNULL_BEGIN /** * An instance of `JSQMessagesBubblesSizeCalculator` is responsible for calculating * message bubble sizes for an instance of `JSQMessagesCollectionViewFlowLayout`. */ @interface JSQMessagesBubblesSizeCalculator : NSObject /** * Initializes and returns a bubble size calculator with the given cache and minimumBubbleWidth. * * @param cache A cache object used to store layout information. * @param minimumBubbleWidth The minimum width for any given message bubble. * @param usesFixedWidthBubbles Specifies whether or not to use fixed-width bubbles. * If `NO` (the default), then bubbles will resize when rotating to landscape. * * @return An initialized `JSQMessagesBubblesSizeCalculator`. */ - (nullable instancetype)initWithCache:(nonnull NSCache *)cache minimumBubbleWidth:(NSUInteger)minimumBubbleWidth usesFixedWidthBubbles:(BOOL)usesFixedWidthBubbles NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.m ================================================ // // 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 "JSQMessagesBubblesSizeCalculator.h" #import "JSQMessagesCollectionView.h" #import "JSQMessagesCollectionViewDataSource.h" #import "JSQMessagesCollectionViewFlowLayout.h" #import "JSQMessageData.h" #import "UIImage+JSQMessages.h" @interface JSQMessagesBubblesSizeCalculator () @property (strong, nonatomic, readonly) NSCache *cache; @property (assign, nonatomic, readonly) NSUInteger minimumBubbleWidth; @property (assign, nonatomic, readonly) BOOL usesFixedWidthBubbles; @property (assign, nonatomic, readonly) NSInteger additionalInset; @property (assign, nonatomic) CGFloat layoutWidthForFixedWidthBubbles; @end @implementation JSQMessagesBubblesSizeCalculator #pragma mark - Init - (instancetype)initWithCache:(NSCache *)cache minimumBubbleWidth:(NSUInteger)minimumBubbleWidth usesFixedWidthBubbles:(BOOL)usesFixedWidthBubbles { NSParameterAssert(cache != nil); NSParameterAssert(minimumBubbleWidth > 0); self = [super init]; if (self) { _cache = cache; _minimumBubbleWidth = minimumBubbleWidth; _usesFixedWidthBubbles = usesFixedWidthBubbles; _layoutWidthForFixedWidthBubbles = 0.0f; // this extra inset value is needed because `boundingRectWithSize:` is slightly off // see comment below _additionalInset = 2; } return self; } - (instancetype)init { NSCache *cache = [NSCache new]; cache.name = @"JSQMessagesBubblesSizeCalculator.cache"; cache.countLimit = 200; return [self initWithCache:cache minimumBubbleWidth:[UIImage jsq_bubbleCompactImage].size.width usesFixedWidthBubbles:NO]; } #pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: cache=%@, minimumBubbleWidth=%@ usesFixedWidthBubbles=%@>", [self class], self.cache, @(self.minimumBubbleWidth), @(self.usesFixedWidthBubbles)]; } #pragma mark - JSQMessagesBubbleSizeCalculating - (void)prepareForResettingLayout:(JSQMessagesCollectionViewFlowLayout *)layout { [self.cache removeAllObjects]; } - (CGSize)messageBubbleSizeForMessageData:(id)messageData atIndexPath:(NSIndexPath *)indexPath withLayout:(JSQMessagesCollectionViewFlowLayout *)layout { NSValue *cachedSize = [self.cache objectForKey:@([messageData messageHash])]; if (cachedSize != nil) { return [cachedSize CGSizeValue]; } CGSize finalSize = CGSizeZero; if ([messageData isMediaMessage]) { finalSize = [[messageData media] mediaViewDisplaySize]; } else { CGSize avatarSize = [self jsq_avatarSizeForMessageData:messageData withLayout:layout]; // from the cell xibs, there is a 2 point space between avatar and bubble CGFloat spacingBetweenAvatarAndBubble = 2.0f; CGFloat horizontalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.left + layout.messageBubbleTextViewTextContainerInsets.right; CGFloat horizontalFrameInsets = layout.messageBubbleTextViewFrameInsets.left + layout.messageBubbleTextViewFrameInsets.right; CGFloat horizontalInsetsTotal = horizontalContainerInsets + horizontalFrameInsets + spacingBetweenAvatarAndBubble; CGFloat maximumTextWidth = [self textBubbleWidthForLayout:layout] - avatarSize.width - layout.messageBubbleLeftRightMargin - horizontalInsetsTotal; CGRect stringRect = [[messageData text] boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) attributes:@{ NSFontAttributeName : layout.messageBubbleFont } context:nil]; CGSize stringSize = CGRectIntegral(stringRect).size; CGFloat verticalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.top + layout.messageBubbleTextViewTextContainerInsets.bottom; CGFloat verticalFrameInsets = layout.messageBubbleTextViewFrameInsets.top + layout.messageBubbleTextViewFrameInsets.bottom; // add extra 2 points of space (`self.additionalInset`), because `boundingRectWithSize:` is slightly off // not sure why. magix. (shrug) if you know, submit a PR CGFloat verticalInsets = verticalContainerInsets + verticalFrameInsets + self.additionalInset; // same as above, an extra 2 points of magix CGFloat finalWidth = MAX(stringSize.width + horizontalInsetsTotal, self.minimumBubbleWidth) + self.additionalInset; finalSize = CGSizeMake(finalWidth, stringSize.height + verticalInsets); } [self.cache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageData messageHash])]; return finalSize; } - (CGSize)jsq_avatarSizeForMessageData:(id)messageData withLayout:(JSQMessagesCollectionViewFlowLayout *)layout { NSString *messageSender = [messageData senderId]; if ([messageSender isEqualToString:[layout.collectionView.dataSource senderId]]) { return layout.outgoingAvatarViewSize; } return layout.incomingAvatarViewSize; } - (CGFloat)textBubbleWidthForLayout:(JSQMessagesCollectionViewFlowLayout *)layout { if (self.usesFixedWidthBubbles) { return [self widthForFixedWidthBubblesWithLayout:layout]; } return layout.itemWidth; } - (CGFloat)widthForFixedWidthBubblesWithLayout:(JSQMessagesCollectionViewFlowLayout *)layout { if (self.layoutWidthForFixedWidthBubbles > 0.0f) { return self.layoutWidthForFixedWidthBubbles; } // also need to add `self.additionalInset` here, see comment above NSInteger horizontalInsets = layout.sectionInset.left + layout.sectionInset.right + self.additionalInset; CGFloat width = CGRectGetWidth(layout.collectionView.bounds) - horizontalInsets; CGFloat height = CGRectGetHeight(layout.collectionView.bounds) - horizontalInsets; self.layoutWidthForFixedWidthBubbles = MIN(width, height); return self.layoutWidthForFixedWidthBubbles; } @end ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.h ================================================ // // 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 // // // Ideas for springy collection view layout taken from Ash Furrow // ASHSpringyCollectionView // https://github.com/AshFurrow/ASHSpringyCollectionView // #import #import "JSQMessagesBubbleSizeCalculating.h" @class JSQMessagesCollectionView; /** * A constant that describes the default height for all label subviews in a `JSQMessagesCollectionViewCell`. * * @see JSQMessagesCollectionViewCell. */ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewCellLabelHeightDefault; /** * A constant that describes the default size for avatar images in a `JSQMessagesCollectionViewFlowLayout`. */ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault; NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesCollectionViewFlowLayout` is a concrete layout object that inherits * from `UICollectionViewFlowLayout` and organizes message items in a vertical list. * Each `JSQMessagesCollectionViewCell` in the layout can display messages of arbitrary sizes and avatar images, * as well as metadata such as a timestamp and sender. * You can easily customize the layout via its properties or its delegate methods * defined in `JSQMessagesCollectionViewDelegateFlowLayout`. * * @see JSQMessagesCollectionViewDelegateFlowLayout. * @see JSQMessagesCollectionViewCell. */ @interface JSQMessagesCollectionViewFlowLayout : UICollectionViewFlowLayout /** * 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 /** * The object that the layout uses to calculate bubble sizes. * The default value is an instance of `JSQMessagesBubblesSizeCalculator`. */ @property (strong, nonatomic) id bubbleSizeCalculator; /** * Specifies whether or not the layout should enable spring behavior dynamics for its items using `UIDynamics`. * * @discussion The default value is `NO`, which disables "springy" or "bouncy" items in the layout. * Set to `YES` if you want items to have spring behavior dynamics. You *must* set this property from `viewDidAppear:` * in your `JSQMessagesViewController` subclass. * * @warning Though this feature is mostly stable, it is still considered an experimental feature. */ @property (assign, nonatomic) BOOL springinessEnabled; /** * Specifies the degree of resistence for the "springiness" of items in the layout. * This property has no effect if `springinessEnabled` is set to `NO`. * * @discussion The default value is `1000`. Increasing this value increases the resistance, that is, items become less "bouncy". * Decrease this value in order to make items more "bouncy". */ @property (assign, nonatomic) NSUInteger springResistanceFactor; /** * Returns the width of items in the layout. */ @property (readonly, nonatomic) CGFloat itemWidth; /** * The font used to display the body a text message in the message bubble of each * `JSQMessagesCollectionViewCell` in the collectionView. * * @discussion The default value is the preferred system font for `UIFontTextStyleBody`. This value must not be `nil`. */ @property (strong, nonatomic) UIFont *messageBubbleFont; /** * The horizontal spacing used to lay out the `messageBubbleContainerView` frame within each `JSQMessagesCollectionViewCell`. * This container view holds the message bubble image and message contents of a cell. * * This value specifies the horizontal spacing between the `messageBubbleContainerView` and * the edge of the collection view cell in which it is displayed. That is, the edge that is opposite the avatar image. * * @discussion The default value is `40.0f` on iPhone and `240.0f` on iPad. This value must be positive. * For *outgoing* messages, this value specifies the amount of spacing from the left most * edge of the collectionView to the left most edge of a message bubble within a cell. * * For *incoming* messages, this value specifies the amount of spacing from the right most * edge of the collectionView to the right most edge of a message bubble within a cell. * * @warning This value may not be exact when the layout object finishes laying out its items, due to the constraints it must satisfy. * This value should be considered more of a recommendation or suggestion to the layout, not an exact value. * * @see JSQMessagesCollectionViewCellIncoming. * @see JSQMessagesCollectionViewCellOutgoing. */ @property (assign, nonatomic) CGFloat messageBubbleLeftRightMargin; /** * The inset of the frame of the text view within the `messageBubbleContainerView` of each `JSQMessagesCollectionViewCell`. * The inset values should be positive and are applied in the following ways: * * 1. The right value insets the text view frame on the side adjacent to the avatar image * (or where the avatar would normally appear). For outgoing messages this is the right side, * for incoming messages this is the left side. * * 2. The left value insets the text view frame on the side opposite the avatar image * (or where the avatar would normally appear). For outgoing messages this is the left side, * for incoming messages this is the right side. * * 3. The top value insets the top of the frame. * * 4. The bottom value insets the bottom of the frame. * * @discussion The default value is `{0.0f, 0.0f, 0.0f, 6.0f}`. * * @warning Adjusting this value is an advanced endeavour and not recommended. * You will only need to adjust this value should you choose to provide your own bubble image assets. * Changing this value may also require you to manually calculate the itemSize for each cell * in the layout by overriding the delegate method `collectionView:layout:sizeForItemAtIndexPath:` */ @property (assign, nonatomic) UIEdgeInsets messageBubbleTextViewFrameInsets; /** * The inset of the text container's layout area within the text view's content area in each `JSQMessagesCollectionViewCell`. * The specified inset values should be positive. * * @discussion The default value is `{7.0f, 14.0f, 7.0f, 14.0f}`. * * @warning Adjusting this value is an advanced endeavour and not recommended. * You will only need to adjust this value should you choose to provide your own bubble image assets. * Changing this value may also require you to manually calculate the itemSize for each cell * in the layout by overriding the delegate method `collectionView:layout:sizeForItemAtIndexPath:` */ @property (assign, nonatomic) UIEdgeInsets messageBubbleTextViewTextContainerInsets; /** * The size of the avatar image view for incoming messages. * * @discussion The default value is `(30.0f, 30.0f)`. Set to `CGSizeZero` to remove incoming avatars. * You may use `kJSQMessagesCollectionViewAvatarSizeDefault` to size your avatars to the default value. */ @property (assign, nonatomic) CGSize incomingAvatarViewSize; /** * The size of the avatar image view for outgoing messages. * * @discussion The default value is `(30.0f, 30.0f)`. Set to `CGSizeZero` to remove outgoing avatars. * You may use `kJSQMessagesCollectionViewAvatarSizeDefault` to size your avatars to the default value. */ @property (assign, nonatomic) CGSize outgoingAvatarViewSize; /** * The maximum number of items that the layout should keep in its cache of layout information. * * @discussion The default value is `200`. A limit of `0` means no limit. This is not a strict limit. */ @property (assign, nonatomic) NSUInteger cacheLimit; /** * Computes and returns the size of the `messageBubbleImageView` property of a `JSQMessagesCollectionViewCell` * at the specified indexPath. * * @param indexPath The index path of the item to be displayed. * * @return The size of the message bubble for the item displayed at indexPath. * * @discussion The layout uses its `bubbleSizeCalculator` object to perform this computation. * The returned size contains the required dimensions to display the entire message contents. * Note, this is *not* the entire cell, but only its message bubble. */ - (CGSize)messageBubbleSizeForItemAtIndexPath:(NSIndexPath *)indexPath; /** * Computes and returns the size of the item specified by indexPath. * * @param indexPath The index path of the item to be displayed. * * @return The size of the item displayed at indexPath. */ - (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m ================================================ // // 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 // // // Ideas for springy collection view layout taken from Ash Furrow // ASHSpringyCollectionView // https://github.com/AshFurrow/ASHSpringyCollectionView // #import "JSQMessagesCollectionViewFlowLayout.h" #import "JSQMessageData.h" #import "JSQMessagesCollectionView.h" #import "JSQMessagesCollectionViewCell.h" #import "JSQMessagesCollectionViewLayoutAttributes.h" #import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h" #import "JSQMessagesBubblesSizeCalculator.h" #import "UIImage+JSQMessages.h" const CGFloat kJSQMessagesCollectionViewCellLabelHeightDefault = 20.0f; const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault = 30.0f; @interface JSQMessagesCollectionViewFlowLayout () @property (strong, nonatomic) UIDynamicAnimator *dynamicAnimator; @property (strong, nonatomic) NSMutableSet *visibleIndexPaths; @property (assign, nonatomic) CGFloat latestDelta; @end @implementation JSQMessagesCollectionViewFlowLayout @dynamic collectionView; @synthesize bubbleSizeCalculator = _bubbleSizeCalculator; #pragma mark - Initialization - (void)jsq_configureFlowLayout { self.scrollDirection = UICollectionViewScrollDirectionVertical; self.sectionInset = UIEdgeInsetsMake(10.0f, 4.0f, 10.0f, 4.0f); self.minimumLineSpacing = 4.0f; _messageBubbleFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { _messageBubbleLeftRightMargin = 240.0f; } else { _messageBubbleLeftRightMargin = 50.0f; } _messageBubbleTextViewFrameInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 6.0f); _messageBubbleTextViewTextContainerInsets = UIEdgeInsetsMake(7.0f, 14.0f, 7.0f, 14.0f); CGSize defaultAvatarSize = CGSizeMake(kJSQMessagesCollectionViewAvatarSizeDefault, kJSQMessagesCollectionViewAvatarSizeDefault); _incomingAvatarViewSize = defaultAvatarSize; _outgoingAvatarViewSize = defaultAvatarSize; _springinessEnabled = NO; _springResistanceFactor = 1000; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jsq_didReceiveApplicationMemoryWarningNotification:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jsq_didReceiveDeviceOrientationDidChangeNotification:) name:UIDeviceOrientationDidChangeNotification object:nil]; } - (instancetype)init { self = [super init]; if (self) { [self jsq_configureFlowLayout]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self jsq_configureFlowLayout]; } + (Class)layoutAttributesClass { return [JSQMessagesCollectionViewLayoutAttributes class]; } + (Class)invalidationContextClass { return [JSQMessagesCollectionViewFlowLayoutInvalidationContext class]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - Setters - (void)setBubbleSizeCalculator:(id)bubbleSizeCalculator { NSParameterAssert(bubbleSizeCalculator != nil); _bubbleSizeCalculator = bubbleSizeCalculator; } - (void)setSpringinessEnabled:(BOOL)springinessEnabled { if (_springinessEnabled == springinessEnabled) { return; } _springinessEnabled = springinessEnabled; if (!springinessEnabled) { [_dynamicAnimator removeAllBehaviors]; [_visibleIndexPaths removeAllObjects]; } [self invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; } - (void)setMessageBubbleFont:(UIFont *)messageBubbleFont { if ([_messageBubbleFont isEqual:messageBubbleFont]) { return; } NSParameterAssert(messageBubbleFont != nil); _messageBubbleFont = messageBubbleFont; [self invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; } - (void)setMessageBubbleLeftRightMargin:(CGFloat)messageBubbleLeftRightMargin { NSParameterAssert(messageBubbleLeftRightMargin >= 0.0f); _messageBubbleLeftRightMargin = ceilf(messageBubbleLeftRightMargin); [self invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; } - (void)setMessageBubbleTextViewTextContainerInsets:(UIEdgeInsets)messageBubbleTextContainerInsets { if (UIEdgeInsetsEqualToEdgeInsets(_messageBubbleTextViewTextContainerInsets, messageBubbleTextContainerInsets)) { return; } _messageBubbleTextViewTextContainerInsets = messageBubbleTextContainerInsets; [self invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; } - (void)setIncomingAvatarViewSize:(CGSize)incomingAvatarViewSize { if (CGSizeEqualToSize(_incomingAvatarViewSize, incomingAvatarViewSize)) { return; } _incomingAvatarViewSize = incomingAvatarViewSize; [self invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; } - (void)setOutgoingAvatarViewSize:(CGSize)outgoingAvatarViewSize { if (CGSizeEqualToSize(_outgoingAvatarViewSize, outgoingAvatarViewSize)) { return; } _outgoingAvatarViewSize = outgoingAvatarViewSize; [self invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; } #pragma mark - Getters - (CGFloat)itemWidth { return CGRectGetWidth(self.collectionView.frame) - self.sectionInset.left - self.sectionInset.right; } - (UIDynamicAnimator *)dynamicAnimator { if (!_dynamicAnimator) { _dynamicAnimator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self]; } return _dynamicAnimator; } - (NSMutableSet *)visibleIndexPaths { if (!_visibleIndexPaths) { _visibleIndexPaths = [NSMutableSet new]; } return _visibleIndexPaths; } - (id)bubbleSizeCalculator { if (_bubbleSizeCalculator == nil) { _bubbleSizeCalculator = [JSQMessagesBubblesSizeCalculator new]; } return _bubbleSizeCalculator; } #pragma mark - Notifications - (void)jsq_didReceiveApplicationMemoryWarningNotification:(NSNotification *)notification { [self jsq_resetLayout]; } - (void)jsq_didReceiveDeviceOrientationDidChangeNotification:(NSNotification *)notification { [self jsq_resetLayout]; [self invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; } #pragma mark - Collection view flow layout - (void)invalidateLayoutWithContext:(JSQMessagesCollectionViewFlowLayoutInvalidationContext *)context { if (context.invalidateDataSourceCounts) { context.invalidateFlowLayoutAttributes = YES; context.invalidateFlowLayoutDelegateMetrics = YES; } if (context.invalidateFlowLayoutAttributes || context.invalidateFlowLayoutDelegateMetrics) { [self jsq_resetDynamicAnimator]; } if (context.invalidateFlowLayoutMessagesCache) { [self jsq_resetLayout]; } [super invalidateLayoutWithContext:context]; } - (void)prepareLayout { [super prepareLayout]; if (self.springinessEnabled) { // pad rect to avoid flickering CGFloat padding = -100.0f; CGRect visibleRect = CGRectInset(self.collectionView.bounds, padding, padding); NSArray *visibleItems = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:visibleRect] copyItems:YES]; NSSet *visibleItemsIndexPaths = [NSSet setWithArray:[visibleItems valueForKey:NSStringFromSelector(@selector(indexPath))]]; [self jsq_removeNoLongerVisibleBehaviorsFromVisibleItemsIndexPaths:visibleItemsIndexPaths]; [self jsq_addNewlyVisibleBehaviorsFromVisibleItems:visibleItems]; } } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *attributesInRect = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES]; if (self.springinessEnabled) { NSMutableArray *attributesInRectCopy = [attributesInRect mutableCopy]; NSArray *dynamicAttributes = [self.dynamicAnimator itemsInRect:rect]; // avoid duplicate attributes // use dynamic animator attribute item instead of regular item, if it exists for (UICollectionViewLayoutAttributes *eachItem in attributesInRect) { for (UICollectionViewLayoutAttributes *eachDynamicItem in dynamicAttributes) { if ([eachItem.indexPath isEqual:eachDynamicItem.indexPath] && eachItem.representedElementCategory == eachDynamicItem.representedElementCategory) { [attributesInRectCopy removeObject:eachItem]; [attributesInRectCopy addObject:eachDynamicItem]; continue; } } } attributesInRect = [attributesInRectCopy copy]; } [attributesInRect enumerateObjectsUsingBlock:^(JSQMessagesCollectionViewLayoutAttributes *attributesItem, NSUInteger idx, BOOL *stop) { if (attributesItem.representedElementCategory == UICollectionElementCategoryCell) { [self jsq_configureMessageCellLayoutAttributes:attributesItem]; } else { attributesItem.zIndex = -1; } }]; return attributesInRect; } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { JSQMessagesCollectionViewLayoutAttributes *customAttributes = (JSQMessagesCollectionViewLayoutAttributes *)[[super layoutAttributesForItemAtIndexPath:indexPath] copy]; if (customAttributes.representedElementCategory == UICollectionElementCategoryCell) { [self jsq_configureMessageCellLayoutAttributes:customAttributes]; } return customAttributes; } - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { if (self.springinessEnabled) { UIScrollView *scrollView = self.collectionView; CGFloat delta = newBounds.origin.y - scrollView.bounds.origin.y; self.latestDelta = delta; CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView]; [self.dynamicAnimator.behaviors enumerateObjectsUsingBlock:^(UIAttachmentBehavior *springBehaviour, NSUInteger idx, BOOL *stop) { [self jsq_adjustSpringBehavior:springBehaviour forTouchLocation:touchLocation]; [self.dynamicAnimator updateItemUsingCurrentState:[springBehaviour.items firstObject]]; }]; } CGRect oldBounds = self.collectionView.bounds; if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) { return YES; } return NO; } - (void)prepareForCollectionViewUpdates:(NSArray *)updateItems { [super prepareForCollectionViewUpdates:updateItems]; [updateItems enumerateObjectsUsingBlock:^(UICollectionViewUpdateItem *updateItem, NSUInteger index, BOOL *stop) { if (updateItem.updateAction == UICollectionUpdateActionInsert) { if (self.springinessEnabled && [self.dynamicAnimator layoutAttributesForCellAtIndexPath:updateItem.indexPathAfterUpdate]) { *stop = YES; } CGFloat collectionViewHeight = CGRectGetHeight(self.collectionView.bounds); JSQMessagesCollectionViewLayoutAttributes *attributes = [JSQMessagesCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:updateItem.indexPathAfterUpdate]; if (attributes.representedElementCategory == UICollectionElementCategoryCell) { [self jsq_configureMessageCellLayoutAttributes:attributes]; } attributes.frame = CGRectMake(0.0f, collectionViewHeight + CGRectGetHeight(attributes.frame), CGRectGetWidth(attributes.frame), CGRectGetHeight(attributes.frame)); if (self.springinessEnabled) { UIAttachmentBehavior *springBehaviour = [self jsq_springBehaviorWithLayoutAttributesItem:attributes]; [self.dynamicAnimator addBehavior:springBehaviour]; } } }]; } #pragma mark - Invalidation utilities - (void)jsq_resetLayout { [self.bubbleSizeCalculator prepareForResettingLayout:self]; [self jsq_resetDynamicAnimator]; } - (void)jsq_resetDynamicAnimator { if (self.springinessEnabled) { [self.dynamicAnimator removeAllBehaviors]; [self.visibleIndexPaths removeAllObjects]; } } #pragma mark - Message cell layout utilities - (CGSize)messageBubbleSizeForItemAtIndexPath:(NSIndexPath *)indexPath { id messageItem = [self.collectionView.dataSource collectionView:self.collectionView messageDataForItemAtIndexPath:indexPath]; return [self.bubbleSizeCalculator messageBubbleSizeForMessageData:messageItem atIndexPath:indexPath withLayout:self]; } - (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGSize messageBubbleSize = [self messageBubbleSizeForItemAtIndexPath:indexPath]; JSQMessagesCollectionViewLayoutAttributes *attributes = (JSQMessagesCollectionViewLayoutAttributes *)[self layoutAttributesForItemAtIndexPath:indexPath]; CGFloat finalHeight = messageBubbleSize.height; finalHeight += attributes.cellTopLabelHeight; finalHeight += attributes.messageBubbleTopLabelHeight; finalHeight += attributes.cellBottomLabelHeight; return CGSizeMake(self.itemWidth, ceilf(finalHeight)); } - (void)jsq_configureMessageCellLayoutAttributes:(JSQMessagesCollectionViewLayoutAttributes *)layoutAttributes { NSIndexPath *indexPath = layoutAttributes.indexPath; CGSize messageBubbleSize = [self messageBubbleSizeForItemAtIndexPath:indexPath]; layoutAttributes.messageBubbleContainerViewWidth = messageBubbleSize.width; layoutAttributes.textViewFrameInsets = self.messageBubbleTextViewFrameInsets; layoutAttributes.textViewTextContainerInsets = self.messageBubbleTextViewTextContainerInsets; layoutAttributes.messageBubbleFont = self.messageBubbleFont; layoutAttributes.incomingAvatarViewSize = self.incomingAvatarViewSize; layoutAttributes.outgoingAvatarViewSize = self.outgoingAvatarViewSize; layoutAttributes.cellTopLabelHeight = [self.collectionView.delegate collectionView:self.collectionView layout:self heightForCellTopLabelAtIndexPath:indexPath]; layoutAttributes.messageBubbleTopLabelHeight = [self.collectionView.delegate collectionView:self.collectionView layout:self heightForMessageBubbleTopLabelAtIndexPath:indexPath]; layoutAttributes.cellBottomLabelHeight = [self.collectionView.delegate collectionView:self.collectionView layout:self heightForCellBottomLabelAtIndexPath:indexPath]; } #pragma mark - Spring behavior utilities - (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; springBehavior.frequency = 1.0f; return springBehavior; } - (void)jsq_addNewlyVisibleBehaviorsFromVisibleItems:(NSArray *)visibleItems { // a "newly visible" item is in `visibleItems` but not in `self.visibleIndexPaths` NSIndexSet *indexSet = [visibleItems indexesOfObjectsPassingTest:^BOOL(UICollectionViewLayoutAttributes *item, NSUInteger index, BOOL *stop) { return ![self.visibleIndexPaths containsObject:item.indexPath]; }]; NSArray *newlyVisibleItems = [visibleItems objectsAtIndexes:indexSet]; CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView]; [newlyVisibleItems enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *item, NSUInteger index, BOOL *stop) { UIAttachmentBehavior *springBehaviour = [self jsq_springBehaviorWithLayoutAttributesItem:item]; [self jsq_adjustSpringBehavior:springBehaviour forTouchLocation:touchLocation]; [self.dynamicAnimator addBehavior:springBehaviour]; [self.visibleIndexPaths addObject:item.indexPath]; }]; } - (void)jsq_removeNoLongerVisibleBehaviorsFromVisibleItemsIndexPaths:(NSSet *)visibleItemsIndexPaths { NSArray *behaviors = self.dynamicAnimator.behaviors; NSIndexSet *indexSet = [behaviors indexesOfObjectsPassingTest:^BOOL(UIAttachmentBehavior *springBehaviour, NSUInteger index, BOOL *stop) { UICollectionViewLayoutAttributes *layoutAttributes = (UICollectionViewLayoutAttributes *)[springBehaviour.items firstObject]; return ![visibleItemsIndexPaths containsObject:layoutAttributes.indexPath]; }]; NSArray *behaviorsToRemove = [self.dynamicAnimator.behaviors objectsAtIndexes:indexSet]; [behaviorsToRemove enumerateObjectsUsingBlock:^(UIAttachmentBehavior *springBehaviour, NSUInteger index, BOOL *stop) { UICollectionViewLayoutAttributes *layoutAttributes = (UICollectionViewLayoutAttributes *)[springBehaviour.items firstObject]; [self.dynamicAnimator removeBehavior:springBehaviour]; [self.visibleIndexPaths removeObject:layoutAttributes.indexPath]; }]; } - (void)jsq_adjustSpringBehavior:(UIAttachmentBehavior *)springBehavior forTouchLocation:(CGPoint)touchLocation { UICollectionViewLayoutAttributes *item = (UICollectionViewLayoutAttributes *)[springBehavior.items firstObject]; CGPoint center = item.center; // if touch is not (0,0) -- adjust item center "in flight" if (!CGPointEqualToPoint(CGPointZero, touchLocation)) { CGFloat distanceFromTouch = fabs(touchLocation.y - springBehavior.anchorPoint.y); CGFloat scrollResistance = distanceFromTouch / self.springResistanceFactor; if (self.latestDelta < 0.0f) { center.y += MAX(self.latestDelta, self.latestDelta * scrollResistance); } else { center.y += MIN(self.latestDelta, self.latestDelta * scrollResistance); } item.center = center; } } @end ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayoutInvalidationContext.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN /** * A `JSQMessagesCollectionViewFlowLayoutInvalidationContext` object specifies properties for * determining whether to recompute the size of items or their position in the layout. * The flow layout object creates instances of this class when it needs to invalidate its contents * in response to changes. You can also create instances when invalidating the flow layout manually. * */ @interface JSQMessagesCollectionViewFlowLayoutInvalidationContext : UICollectionViewFlowLayoutInvalidationContext /** * A boolean indicating whether to empty the messages layout information cache for items and views in the layout. * The default value is `NO`. */ @property (nonatomic, assign) BOOL invalidateFlowLayoutMessagesCache; /** * Creates and returns a new `JSQMessagesCollectionViewFlowLayoutInvalidationContext` object. * * @discussion When you need to invalidate the `JSQMessagesCollectionViewFlowLayout` object for your * `JSQMessagesViewController` subclass, you should use this method to instantiate a new invalidation * context and pass this object to `invalidateLayoutWithContext:`. * * @return An initialized invalidation context object. */ + (instancetype)context; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayoutInvalidationContext.m ================================================ // // 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 "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h" @implementation JSQMessagesCollectionViewFlowLayoutInvalidationContext #pragma mark - Initialization - (instancetype)init { self = [super init]; if (self) { self.invalidateFlowLayoutDelegateMetrics = NO; self.invalidateFlowLayoutAttributes = NO; _invalidateFlowLayoutMessagesCache = NO; } return self; } + (instancetype)context { JSQMessagesCollectionViewFlowLayoutInvalidationContext *context = [[JSQMessagesCollectionViewFlowLayoutInvalidationContext alloc] init]; context.invalidateFlowLayoutDelegateMetrics = YES; context.invalidateFlowLayoutAttributes = YES; return context; } #pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: invalidateFlowLayoutDelegateMetrics=%@, invalidateFlowLayoutAttributes=%@, invalidateDataSourceCounts=%@, invalidateFlowLayoutMessagesCache=%@>", [self class], @(self.invalidateFlowLayoutDelegateMetrics), @(self.invalidateFlowLayoutAttributes), @(self.invalidateDataSourceCounts), @(self.invalidateFlowLayoutMessagesCache)]; } @end ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN /** * A `JSQMessagesCollectionViewLayoutAttributes` is an object that manages the layout-related attributes * for a given `JSQMessagesCollectionViewCell` in a `JSQMessagesCollectionView`. */ @interface JSQMessagesCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes /** * The font used to display the body of a text message in a message bubble within a `JSQMessagesCollectionViewCell`. * This value must not be `nil`. */ @property (strong, nonatomic) UIFont *messageBubbleFont; /** * The width of the `messageBubbleContainerView` of a `JSQMessagesCollectionViewCell`. * This value should be greater than `0.0`. * * @see JSQMessagesCollectionViewCell. */ @property (assign, nonatomic) CGFloat messageBubbleContainerViewWidth; /** * The inset of the text container's layout area within the text view's content area in a `JSQMessagesCollectionViewCell`. * The specified inset values should be greater than or equal to `0.0`. */ @property (assign, nonatomic) UIEdgeInsets textViewTextContainerInsets; /** * The inset of the frame of the text view within a `JSQMessagesCollectionViewCell`. * * @discussion The inset values should be greater than or equal to `0.0` and are applied in the following ways: * * 1. The right value insets the text view frame on the side adjacent to the avatar image * (or where the avatar would normally appear). For outgoing messages this is the right side, * for incoming messages this is the left side. * * 2. The left value insets the text view frame on the side opposite the avatar image * (or where the avatar would normally appear). For outgoing messages this is the left side, * for incoming messages this is the right side. * * 3. The top value insets the top of the frame. * * 4. The bottom value insets the bottom of the frame. */ @property (assign, nonatomic) UIEdgeInsets textViewFrameInsets; /** * The size of the `avatarImageView` of a `JSQMessagesCollectionViewCellIncoming`. * The size values should be greater than or equal to `0.0`. * * @see JSQMessagesCollectionViewCellIncoming. */ @property (assign, nonatomic) CGSize incomingAvatarViewSize; /** * The size of the `avatarImageView` of a `JSQMessagesCollectionViewCellOutgoing`. * The size values should be greater than or equal to `0.0`. * * @see `JSQMessagesCollectionViewCellOutgoing`. */ @property (assign, nonatomic) CGSize outgoingAvatarViewSize; /** * The height of the `cellTopLabel` of a `JSQMessagesCollectionViewCell`. * This value should be greater than or equal to `0.0`. * * @see JSQMessagesCollectionViewCell. */ @property (assign, nonatomic) CGFloat cellTopLabelHeight; /** * The height of the `messageBubbleTopLabel` of a `JSQMessagesCollectionViewCell`. * This value should be greater than or equal to `0.0`. * * @see JSQMessagesCollectionViewCell. */ @property (assign, nonatomic) CGFloat messageBubbleTopLabelHeight; /** * The height of the `cellBottomLabel` of a `JSQMessagesCollectionViewCell`. * This value should be greater than or equal to `0.0`. * * @see JSQMessagesCollectionViewCell. */ @property (assign, nonatomic) CGFloat cellBottomLabelHeight; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.m ================================================ // // 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 "JSQMessagesCollectionViewLayoutAttributes.h" @implementation JSQMessagesCollectionViewLayoutAttributes #pragma mark - Init - (instancetype)init { self = [super init]; if (self) { _messageBubbleFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; _messageBubbleContainerViewWidth = 320.0f; } return self; } #pragma mark - Setters - (void)setMessageBubbleFont:(UIFont *)messageBubbleFont { NSParameterAssert(messageBubbleFont != nil); _messageBubbleFont = messageBubbleFont; } - (void)setMessageBubbleContainerViewWidth:(CGFloat)messageBubbleContainerViewWidth { NSParameterAssert(messageBubbleContainerViewWidth > 0.0f); _messageBubbleContainerViewWidth = ceilf(messageBubbleContainerViewWidth); } - (void)setIncomingAvatarViewSize:(CGSize)incomingAvatarViewSize { NSParameterAssert(incomingAvatarViewSize.width >= 0.0f && incomingAvatarViewSize.height >= 0.0f); _incomingAvatarViewSize = [self jsq_correctedAvatarSizeFromSize:incomingAvatarViewSize]; } - (void)setOutgoingAvatarViewSize:(CGSize)outgoingAvatarViewSize { NSParameterAssert(outgoingAvatarViewSize.width >= 0.0f && outgoingAvatarViewSize.height >= 0.0f); _outgoingAvatarViewSize = [self jsq_correctedAvatarSizeFromSize:outgoingAvatarViewSize]; } - (void)setCellTopLabelHeight:(CGFloat)cellTopLabelHeight { NSParameterAssert(cellTopLabelHeight >= 0.0f); _cellTopLabelHeight = [self jsq_correctedLabelHeightForHeight:cellTopLabelHeight]; } - (void)setMessageBubbleTopLabelHeight:(CGFloat)messageBubbleTopLabelHeight { NSParameterAssert(messageBubbleTopLabelHeight >= 0.0f); _messageBubbleTopLabelHeight = [self jsq_correctedLabelHeightForHeight:messageBubbleTopLabelHeight]; } - (void)setCellBottomLabelHeight:(CGFloat)cellBottomLabelHeight { NSParameterAssert(cellBottomLabelHeight >= 0.0f); _cellBottomLabelHeight = [self jsq_correctedLabelHeightForHeight:cellBottomLabelHeight]; } #pragma mark - Utilities - (CGSize)jsq_correctedAvatarSizeFromSize:(CGSize)size { return CGSizeMake(ceilf(size.width), ceilf(size.height)); } - (CGFloat)jsq_correctedLabelHeightForHeight:(CGFloat)height { return ceilf(height); } #pragma mark - NSObject - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[self class]]) { return NO; } if (self.representedElementCategory == UICollectionElementCategoryCell) { JSQMessagesCollectionViewLayoutAttributes *layoutAttributes = (JSQMessagesCollectionViewLayoutAttributes *)object; if (![layoutAttributes.messageBubbleFont isEqual:self.messageBubbleFont] || !UIEdgeInsetsEqualToEdgeInsets(layoutAttributes.textViewFrameInsets, self.textViewFrameInsets) || !UIEdgeInsetsEqualToEdgeInsets(layoutAttributes.textViewTextContainerInsets, self.textViewTextContainerInsets) || !CGSizeEqualToSize(layoutAttributes.incomingAvatarViewSize, self.incomingAvatarViewSize) || !CGSizeEqualToSize(layoutAttributes.outgoingAvatarViewSize, self.outgoingAvatarViewSize) || (int)layoutAttributes.messageBubbleContainerViewWidth != (int)self.messageBubbleContainerViewWidth || (int)layoutAttributes.cellTopLabelHeight != (int)self.cellTopLabelHeight || (int)layoutAttributes.messageBubbleTopLabelHeight != (int)self.messageBubbleTopLabelHeight || (int)layoutAttributes.cellBottomLabelHeight != (int)self.cellBottomLabelHeight) { return NO; } } return [super isEqual:object]; } - (NSUInteger)hash { return [self.indexPath hash]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { JSQMessagesCollectionViewLayoutAttributes *copy = [super copyWithZone:zone]; if (copy.representedElementCategory != UICollectionElementCategoryCell) { return copy; } copy.messageBubbleFont = self.messageBubbleFont; copy.messageBubbleContainerViewWidth = self.messageBubbleContainerViewWidth; copy.textViewFrameInsets = self.textViewFrameInsets; copy.textViewTextContainerInsets = self.textViewTextContainerInsets; copy.incomingAvatarViewSize = self.incomingAvatarViewSize; copy.outgoingAvatarViewSize = self.outgoingAvatarViewSize; copy.cellTopLabelHeight = self.cellTopLabelHeight; copy.messageBubbleTopLabelHeight = self.messageBubbleTopLabelHeight; copy.cellBottomLabelHeight = self.cellBottomLabelHeight; return copy; } @end ================================================ FILE: JSQMessagesViewController/Model/JSQAudioMediaItem.h ================================================ // // 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 "JSQMediaItem.h" #import "JSQAudioMediaViewAttributes.h" #import @class JSQAudioMediaItem; NS_ASSUME_NONNULL_BEGIN @protocol JSQAudioMediaItemDelegate /** * Tells the delegate if the specified `JSQAudioMediaItem` changes the sound category or categoryOptions, or if an error occurs. */ - (void)audioMediaItem:(JSQAudioMediaItem *)audioMediaItem didChangeAudioCategory:(NSString *)category options:(AVAudioSessionCategoryOptions)options error:(nullable NSError *)error; @end /** * The `JSQAudioMediaItem` class is a concrete `JSQMediaItem` subclass that implements the `JSQMessageMediaData` protocol * and represents an audio media message. An initialized `JSQAudioMediaItem` object can be passed * to a `JSQMediaMessage` object during its initialization to construct a valid media message object. * You may wish to subclass `JSQAudioMediaItem` to provide additional functionality or behavior. */ @interface JSQAudioMediaItem : JSQMediaItem /** * The delegate object for audio event notifications. */ @property (nonatomic, weak, nullable) id delegate; /** * The view attributes to configure the appearance of the audio media view. */ @property (nonatomic, strong, readonly) JSQAudioMediaViewAttributes *audioViewAttributes; /** * A data object that contains an audio resource. */ @property (nonatomic, strong, nullable) NSData *audioData; /** * Initializes and returns a audio media item having the given audioData. * * @param audioData The data object that contains the audio resource. * @param audioViewAttributes The view attributes to configure the appearance of the audio media view. * * @return An initialized `JSQAudioMediaItem`. * * @discussion If the audio must be downloaded from the network, * you may initialize a `JSQVideoMediaItem` with a `nil` audioData. * Once the audio is available you can set the `audioData` property. */ - (instancetype)initWithData:(nullable NSData *)audioData audioViewAttributes:(JSQAudioMediaViewAttributes *)audioViewAttributes NS_DESIGNATED_INITIALIZER; /** * Initializes and returns a default audio media item. * * @return An initialized `JSQAudioMediaItem`. * * @discussion You must set `audioData` to enable the play button. */ - (instancetype)init; /** Initializes and returns a default audio media using the specified view attributes. @param audioViewAttributes The view attributes to configure the appearance of the audio media view. @return An initialized `JSQAudioMediaItem`. */ - (instancetype)initWithAudioViewAttributes:(JSQAudioMediaViewAttributes *)audioViewAttributes; /** * Initializes and returns an audio media item having the given audioData. * * @param audioData The data object that contains the audio resource. * * @return An initialized `JSQAudioMediaItem`. * * @discussion If the audio must be downloaded from the network, * you may initialize a `JSQAudioMediaItem` with a `nil` audioData. * Once the audio is available you can set the `audioData` property. */ - (instancetype)initWithData:(nullable NSData *)audioData; /** * Sets or updates the data object in an audio media item with the data specified at audioURL. * * @param audioURL A File URL containing the location of the audio data. */ - (void)setAudioDataWithUrl:(nonnull NSURL *)audioURL; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQAudioMediaItem.m ================================================ // // 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 "JSQAudioMediaItem.h" #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" #import "UIImage+JSQMessages.h" #import "UIColor+JSQMessages.h" @interface JSQAudioMediaItem () @property (strong, nonatomic) UIView *cachedMediaView; @property (strong, nonatomic) UIButton *playButton; @property (strong, nonatomic) UIProgressView *progressView; @property (strong, nonatomic) UILabel *progressLabel; @property (strong, nonatomic) NSTimer *progressTimer; @property (strong, nonatomic) AVAudioPlayer *audioPlayer; @end @implementation JSQAudioMediaItem #pragma mark - Initialization - (instancetype)initWithData:(NSData *)audioData audioViewAttributes:(JSQAudioMediaViewAttributes *)audioViewAttributes { NSParameterAssert(audioViewAttributes != nil); self = [super init]; if (self) { _cachedMediaView = nil; _audioData = [audioData copy]; _audioViewAttributes = audioViewAttributes; } return self; } - (instancetype)initWithData:(NSData *)audioData { return [self initWithData:audioData audioViewAttributes:[[JSQAudioMediaViewAttributes alloc] init]]; } - (instancetype)initWithAudioViewAttributes:(JSQAudioMediaViewAttributes *)audioViewAttributes { return [self initWithData:nil audioViewAttributes:audioViewAttributes]; } - (instancetype)init { return [self initWithData:nil audioViewAttributes:[[JSQAudioMediaViewAttributes alloc] init]]; } - (void)dealloc { _audioData = nil; [self clearCachedMediaViews]; } - (void)clearCachedMediaViews { [_audioPlayer stop]; _audioPlayer = nil; _playButton = nil; _progressView = nil; _progressLabel = nil; [self stopProgressTimer]; _cachedMediaView = nil; [super clearCachedMediaViews]; } #pragma mark - Setters - (void)setAudioData:(NSData *)audioData { _audioData = [audioData copy]; [self clearCachedMediaViews]; } - (void)setAudioDataWithUrl:(NSURL *)audioURL { _audioData = [NSData dataWithContentsOfURL:audioURL]; [self clearCachedMediaViews]; } - (void)setAppliesMediaViewMaskAsOutgoing:(BOOL)appliesMediaViewMaskAsOutgoing { [super setAppliesMediaViewMaskAsOutgoing:appliesMediaViewMaskAsOutgoing]; _cachedMediaView = nil; } #pragma mark - Private - (void)startProgressTimer { self.progressTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(updateProgressTimer:) userInfo:nil repeats:YES]; } - (void)stopProgressTimer { [_progressTimer invalidate]; _progressTimer = nil; } - (void)updateProgressTimer:(NSTimer *)sender { if (self.audioPlayer.playing) { self.progressView.progress = self.audioPlayer.currentTime / self.audioPlayer.duration; self.progressLabel.text = [self timestampString:self.audioPlayer.currentTime forDuration:self.audioPlayer.duration]; } } - (NSString *)timestampString:(NSTimeInterval)currentTime forDuration:(NSTimeInterval)duration { // print the time as 0:ss or ss.x up to 59 seconds // print the time as m:ss up to 59:59 seconds // print the time as h:mm:ss for anything longer if (duration < 60) { if (self.audioViewAttributes.showFractionalSeconds) { return [NSString stringWithFormat:@"%.01f", currentTime]; } else if (currentTime < duration) { return [NSString stringWithFormat:@"0:%02d", (int)round(currentTime)]; } return [NSString stringWithFormat:@"0:%02d", (int)ceil(currentTime)]; } else if (duration < 3600) { return [NSString stringWithFormat:@"%d:%02d", (int)currentTime / 60, (int)currentTime % 60]; } return [NSString stringWithFormat:@"%d:%02d:%02d", (int)currentTime / 3600, (int)currentTime / 60, (int)currentTime % 60]; } - (void)onPlayButton:(UIButton *)sender { NSString *category = [AVAudioSession sharedInstance].category; AVAudioSessionCategoryOptions options = [AVAudioSession sharedInstance].categoryOptions; if (category != self.audioViewAttributes.audioCategory || options != self.audioViewAttributes.audioCategoryOptions) { NSError *error = nil; [[AVAudioSession sharedInstance] setCategory:self.audioViewAttributes.audioCategory withOptions:self.audioViewAttributes.audioCategoryOptions error:&error]; if (self.delegate) { [self.delegate audioMediaItem:self didChangeAudioCategory:category options:options error:error]; } } if (self.audioPlayer.playing) { self.playButton.selected = NO; [self stopProgressTimer]; [self.audioPlayer stop]; } else { // fade the button from play to pause [UIView transitionWithView:self.playButton duration:.2 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ self.playButton.selected = YES; } completion:nil]; [self startProgressTimer]; [self.audioPlayer play]; } } #pragma mark - AVAudioPlayerDelegate - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { // set progress to full, then fade back to the default state [self stopProgressTimer]; self.progressView.progress = 1; [UIView transitionWithView:self.cachedMediaView duration:.2 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ self.progressView.progress = 0; self.playButton.selected = NO; self.progressLabel.text = [self timestampString:self.audioPlayer.duration forDuration:self.audioPlayer.duration]; } completion:nil]; } #pragma mark - JSQMessageMediaData protocol - (CGSize)mediaViewDisplaySize { return CGSizeMake(160.0f, self.audioViewAttributes.controlInsets.top + self.audioViewAttributes.controlInsets.bottom + self.audioViewAttributes.playButtonImage.size.height); } - (UIView *)mediaView { if (self.audioData && self.cachedMediaView == nil) { if (self.audioData) { self.audioPlayer = [[AVAudioPlayer alloc] initWithData:self.audioData error:nil]; self.audioPlayer.delegate = self; } // reverse the insets based on the message direction CGFloat leftInset, rightInset; if (self.appliesMediaViewMaskAsOutgoing) { leftInset = self.audioViewAttributes.controlInsets.left; rightInset = self.audioViewAttributes.controlInsets.right; } else { leftInset = self.audioViewAttributes.controlInsets.right; rightInset = self.audioViewAttributes.controlInsets.left; } // create container view for the various controls CGSize size = [self mediaViewDisplaySize]; UIView * playView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, size.width, size.height)]; playView.backgroundColor = self.audioViewAttributes.backgroundColor; playView.contentMode = UIViewContentModeCenter; playView.clipsToBounds = YES; // create the play button CGRect buttonFrame = CGRectMake(leftInset, self.audioViewAttributes.controlInsets.top, self.audioViewAttributes.playButtonImage.size.width, self.audioViewAttributes.playButtonImage.size.height); self.playButton = [[UIButton alloc] initWithFrame:buttonFrame]; [self.playButton setImage:self.audioViewAttributes.playButtonImage forState:UIControlStateNormal]; [self.playButton setImage:self.audioViewAttributes.pauseButtonImage forState:UIControlStateSelected]; [self.playButton addTarget:self action:@selector(onPlayButton:) forControlEvents:UIControlEventTouchUpInside]; [playView addSubview:self.playButton]; // create a label to show the duration / elapsed time NSString *durationString = [self timestampString:self.audioPlayer.duration forDuration:self.audioPlayer.duration]; NSString *maxWidthString = [@"" stringByPaddingToLength:[durationString length] withString:@"0" startingAtIndex:0]; // this is cheesy, but it centers the progress bar without extra space and // without causing it to wiggle from side to side as the label text changes CGSize labelSize = CGSizeMake(36, 18); if ([durationString length] < 4) { labelSize = CGSizeMake(18,18); } else if ([durationString length] < 5) { labelSize = CGSizeMake(24,18); } else if ([durationString length] < 6) { labelSize = CGSizeMake(30, 18); } CGRect labelFrame = CGRectMake(size.width - labelSize.width - rightInset, self.audioViewAttributes.controlInsets.top, labelSize.width, labelSize.height); self.progressLabel = [[UILabel alloc] initWithFrame:labelFrame]; self.progressLabel.textAlignment = NSTextAlignmentLeft; self.progressLabel.adjustsFontSizeToFitWidth = YES; self.progressLabel.textColor = self.audioViewAttributes.tintColor; self.progressLabel.font = self.audioViewAttributes.labelFont; self.progressLabel.text = maxWidthString; // sizeToFit adjusts the frame's height to the font [self.progressLabel sizeToFit]; labelFrame.origin.x = size.width - self.progressLabel.frame.size.width - rightInset; labelFrame.origin.y = ((size.height - self.progressLabel.frame.size.height) / 2); labelFrame.size.width = self.progressLabel.frame.size.width; labelFrame.size.height = self.progressLabel.frame.size.height; self.progressLabel.frame = labelFrame; self.progressLabel.text = durationString; [playView addSubview:self.progressLabel]; // create a progress bar self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; CGFloat xOffset = self.playButton.frame.origin.x + self.playButton.frame.size.width + self.audioViewAttributes.controlPadding; CGFloat width = labelFrame.origin.x - xOffset - self.audioViewAttributes.controlPadding; self.progressView.frame = CGRectMake(xOffset, (size.height - self.progressView.frame.size.height) / 2, width, self.progressView.frame.size.height); self.progressView.tintColor = self.audioViewAttributes.tintColor; [playView addSubview:self.progressView]; [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:playView isOutgoing:self.appliesMediaViewMaskAsOutgoing]; self.cachedMediaView = playView; } return self.cachedMediaView; } - (NSUInteger)mediaHash { return self.hash; } #pragma mark - NSObject - (BOOL)isEqual:(id)object { if (![super isEqual:object]) { return NO; } JSQAudioMediaItem *audioItem = (JSQAudioMediaItem *)object; if (self.audioData && ![self.audioData isEqualToData:audioItem.audioData]) { return NO; } return YES; } - (NSUInteger)hash { return super.hash ^ self.audioData.hash; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: audioData=%ld bytes, appliesMediaViewMaskAsOutgoing=%@>", [self class], (unsigned long)[self.audioData length], @(self.appliesMediaViewMaskAsOutgoing)]; } #pragma mark - NSCoding - (instancetype)initWithCoder:(NSCoder *)aDecoder { NSData *data = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(audioData))]; return [self initWithData:data]; } - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeObject:self.audioData forKey:NSStringFromSelector(@selector(audioData))]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { JSQAudioMediaItem *copy = [[[self class] allocWithZone:zone] initWithData:self.audioData audioViewAttributes:self.audioViewAttributes]; copy.appliesMediaViewMaskAsOutgoing = self.appliesMediaViewMaskAsOutgoing; return copy; } @end ================================================ FILE: JSQMessagesViewController/Model/JSQLocationMediaItem.h ================================================ // // 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 #import /** * A completion handler block for a `JSQLocationMediaItem`. See `setLocation: withCompletionHandler:`. */ typedef void (^JSQLocationMediaItemCompletionBlock)(void); #import "JSQMediaItem.h" NS_ASSUME_NONNULL_BEGIN /** * The `JSQLocationMediaItem` class is a concrete `JSQMediaItem` subclass that implements the `JSQMessageMediaData` protocol * and represents a location media message. An initialized `JSQLocationMediaItem` object can be passed * to a `JSQMediaMessage` object during its initialization to construct a valid media message object. * You may wish to subclass `JSQLocationMediaItem` to provide additional functionality or behavior. */ @interface JSQLocationMediaItem : JSQMediaItem /** * The location for the media item. The default value is `nil`. */ @property (copy, nonatomic, nullable) CLLocation *location; /** * The coordinate of the location property. */ @property (readonly, nonatomic) CLLocationCoordinate2D coordinate; /** * Initializes and returns a location media item object having the given location. * * @param location The location for the media item. This value may be `nil`. * * @return An initialized `JSQLocationMediaItem`. * * @discussion If the location data must be dowloaded from the network, * you may initialize a `JSQLocationMediaItem` object with a `nil` location. * Once the location data has been retrieved, you can then set the location property * using `setLocation: withCompletionHandler:` */ - (instancetype)initWithLocation:(nullable CLLocation *)location; /** * Sets the specified location for the location media item and immediately begins creating * a map view snapshot image on a background thread. The map view zooms to a default region whose center point * is the location coordinate and whose span is 500 meters for both the latitudinal and longitudinal meters. * * The specified block is executed upon completion of creating the snapshot image and is executed on the app’s main thread. * * @param location The location for the media item. * @param completion The block to call after the map view snapshot for the given location has been created. */ - (void)setLocation:(nullable CLLocation *)location withCompletionHandler:(nullable JSQLocationMediaItemCompletionBlock)completion; /** * Sets the specified location for the location media item and immediately begins creating * a map view snapshot image on a background thread. * * The specified block is executed upon completion of creating the snapshot image and is executed on the app’s main thread. * * @param location The location for the media item. * @param region The map region that you want to capture. * @param completion The block to call after the map view snapshot for the given location has been created. */ - (void)setLocation:(nullable CLLocation *)location region:(MKCoordinateRegion)region withCompletionHandler:(nullable JSQLocationMediaItemCompletionBlock)completion; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQLocationMediaItem.m ================================================ // // 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 "JSQLocationMediaItem.h" #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" #import @interface JSQLocationMediaItem () @property (strong, nonatomic) UIImage *cachedMapSnapshotImage; @property (strong, nonatomic) UIImageView *cachedMapImageView; @end @implementation JSQLocationMediaItem #pragma mark - Initialization - (instancetype)initWithLocation:(CLLocation *)location { self = [super init]; if (self) { [self setLocation:location withCompletionHandler:nil]; } return self; } - (void)clearCachedMediaViews { [super clearCachedMediaViews]; _cachedMapImageView = nil; } #pragma mark - Setters - (void)setLocation:(CLLocation *)location { [self setLocation:location withCompletionHandler:nil]; } - (void)setAppliesMediaViewMaskAsOutgoing:(BOOL)appliesMediaViewMaskAsOutgoing { [super setAppliesMediaViewMaskAsOutgoing:appliesMediaViewMaskAsOutgoing]; _cachedMapSnapshotImage = nil; _cachedMapImageView = nil; } #pragma mark - Map snapshot - (void)setLocation:(CLLocation *)location withCompletionHandler:(JSQLocationMediaItemCompletionBlock)completion { [self setLocation:location region:MKCoordinateRegionMakeWithDistance(location.coordinate, 500.0, 500.0) withCompletionHandler:completion]; } - (void)setLocation:(CLLocation *)location region:(MKCoordinateRegion)region withCompletionHandler:(JSQLocationMediaItemCompletionBlock)completion { _location = [location copy]; _cachedMapSnapshotImage = nil; _cachedMapImageView = nil; if (_location == nil) { return; } [self createMapViewSnapshotForLocation:_location coordinateRegion:region withCompletionHandler:completion]; } - (void)createMapViewSnapshotForLocation:(CLLocation *)location coordinateRegion:(MKCoordinateRegion)region withCompletionHandler:(JSQLocationMediaItemCompletionBlock)completion { NSParameterAssert(location != nil); MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init]; options.region = region; options.size = [self mediaViewDisplaySize]; options.scale = [UIScreen mainScreen].scale; MKMapSnapshotter *snapShotter = [[MKMapSnapshotter alloc] initWithOptions:options]; [snapShotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { if (snapshot == nil) { NSLog(@"%s Error creating map snapshot: %@", __PRETTY_FUNCTION__, error); return; } MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:nil]; CGPoint coordinatePoint = [snapshot pointForCoordinate:location.coordinate]; UIImage *image = snapshot.image; coordinatePoint.x += pin.centerOffset.x - (CGRectGetWidth(pin.bounds) / 2.0); coordinatePoint.y += pin.centerOffset.y - (CGRectGetHeight(pin.bounds) / 2.0); UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); { [image drawAtPoint:CGPointZero]; [pin.image drawAtPoint:coordinatePoint]; self.cachedMapSnapshotImage = UIGraphicsGetImageFromCurrentImageContext(); } UIGraphicsEndImageContext(); if (completion) { dispatch_async(dispatch_get_main_queue(), completion); } }]; } #pragma mark - MKAnnotation - (CLLocationCoordinate2D)coordinate { return self.location.coordinate; } #pragma mark - JSQMessageMediaData protocol - (UIView *)mediaView { if (self.location == nil || self.cachedMapSnapshotImage == nil) { return nil; } if (self.cachedMapImageView == nil) { UIImageView *imageView = [[UIImageView alloc] initWithImage:self.cachedMapSnapshotImage]; imageView.contentMode = UIViewContentModeScaleAspectFill; imageView.clipsToBounds = YES; [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:imageView isOutgoing:self.appliesMediaViewMaskAsOutgoing]; self.cachedMapImageView = imageView; } return self.cachedMapImageView; } - (NSUInteger)mediaHash { return self.hash; } - (NSString *)mediaDataType { return (NSString *)kUTTypeURL; } - (id)mediaData { NSString *locationAsGoogleMapsString = [NSString stringWithFormat:@"http://maps.apple.com/?ll=%f,%f&z=18&q=%%20", self.coordinate.latitude, self.coordinate.longitude ]; NSURL *locationURL = [[NSURL alloc] initWithString:locationAsGoogleMapsString]; return locationURL; } #pragma mark - NSObject - (BOOL)isEqual:(id)object { if (![super isEqual:object]) { return NO; } JSQLocationMediaItem *locationItem = (JSQLocationMediaItem *)object; return [self.location isEqual:locationItem.location]; } - (NSUInteger)hash { return super.hash ^ self.location.hash; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: location=%@, appliesMediaViewMaskAsOutgoing=%@>", [self class], self.location, @(self.appliesMediaViewMaskAsOutgoing)]; } #pragma mark - NSCoding - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { CLLocation *location = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(location))]; [self setLocation:location withCompletionHandler:nil]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeObject:self.location forKey:NSStringFromSelector(@selector(location))]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { JSQLocationMediaItem *copy = [[[self class] allocWithZone:zone] initWithLocation:self.location]; copy.appliesMediaViewMaskAsOutgoing = self.appliesMediaViewMaskAsOutgoing; return copy; } @end ================================================ FILE: JSQMessagesViewController/Model/JSQMediaItem.h ================================================ // // 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 "JSQMessageMediaData.h" NS_ASSUME_NONNULL_BEGIN /** * The `JSQMediaItem` class is an abstract base class for media item model objects that represents * a single media attachment for a user message. It provides some default behavior for media items, * including a default mediaViewDisplaySize, a default mediaPlaceholderView, and view masking as * specified by appliesMediaViewMaskAsOutgoing. * * @warning This class is intended to be subclassed. You should not use it directly. * * @see JSQLocationMediaItem. * @see JSQPhotoMediaItem. * @see JSQVideoMediaItem. */ @interface JSQMediaItem : NSObject /** * A boolean value indicating whether this media item should apply * an outgoing or incoming bubble image mask to its media views. * Specify `YES` for an outgoing mask, and `NO` for an incoming mask. * The default value is `YES`. */ @property (assign, nonatomic) BOOL appliesMediaViewMaskAsOutgoing; /** * Initializes and returns a media item with the specified value for maskAsOutgoing. * * @param maskAsOutgoing A boolean value indicating whether this media item should apply * an outgoing or incoming bubble image mask to its media views. * * @return An initialized `JSQMediaItem` object. */ - (instancetype)initWithMaskAsOutgoing:(BOOL)maskAsOutgoing; /** * Clears any media view or media placeholder view that the item has cached. */ - (void)clearCachedMediaViews; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMediaItem.m ================================================ // // 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 "JSQMediaItem.h" #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" @interface JSQMediaItem () @property (strong, nonatomic) UIView *cachedPlaceholderView; @end @implementation JSQMediaItem #pragma mark - Initialization - (instancetype)init { return [self initWithMaskAsOutgoing:YES]; } - (instancetype)initWithMaskAsOutgoing:(BOOL)maskAsOutgoing { self = [super init]; 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]; } - (void)setAppliesMediaViewMaskAsOutgoing:(BOOL)appliesMediaViewMaskAsOutgoing { _appliesMediaViewMaskAsOutgoing = appliesMediaViewMaskAsOutgoing; _cachedPlaceholderView = nil; } - (void)clearCachedMediaViews { _cachedPlaceholderView = nil; } #pragma mark - Notifications - (void)didReceiveMemoryWarningNotification:(NSNotification *)notification { [self clearCachedMediaViews]; } #pragma mark - JSQMessageMediaData protocol - (UIView *)mediaView { NSAssert(NO, @"Error! required method not implemented in subclass. Need to implement %s", __PRETTY_FUNCTION__); return nil; } - (CGSize)mediaViewDisplaySize { if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { return CGSizeMake(315.0f, 225.0f); } return CGSizeMake(210.0f, 150.0f); } - (UIView *)mediaPlaceholderView { if (self.cachedPlaceholderView == nil) { CGSize size = [self mediaViewDisplaySize]; UIView *view = [JSQMessagesMediaPlaceholderView viewWithActivityIndicator]; view.frame = CGRectMake(0.0f, 0.0f, size.width, size.height); [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:view isOutgoing:self.appliesMediaViewMaskAsOutgoing]; self.cachedPlaceholderView = view; } return self.cachedPlaceholderView; } - (NSUInteger)mediaHash { return self.hash; } #pragma mark - NSObject - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[self class]]) { return NO; } JSQMediaItem *item = (JSQMediaItem *)object; return self.appliesMediaViewMaskAsOutgoing == item.appliesMediaViewMaskAsOutgoing; } - (NSUInteger)hash { return [NSNumber numberWithBool:self.appliesMediaViewMaskAsOutgoing].hash; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: appliesMediaViewMaskAsOutgoing=%@>", [self class], @(self.appliesMediaViewMaskAsOutgoing)]; } - (id)debugQuickLookObject { return [self mediaView] ?: [self mediaPlaceholderView]; } #pragma mark - NSCoding - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { _appliesMediaViewMaskAsOutgoing = [aDecoder decodeBoolForKey:NSStringFromSelector(@selector(appliesMediaViewMaskAsOutgoing))]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeBool:self.appliesMediaViewMaskAsOutgoing forKey:NSStringFromSelector(@selector(appliesMediaViewMaskAsOutgoing))]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { return [[[self class] allocWithZone:zone] initWithMaskAsOutgoing:self.appliesMediaViewMaskAsOutgoing]; } @end ================================================ FILE: JSQMessagesViewController/Model/JSQMessage.h ================================================ // // 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 #import "JSQMessageData.h" NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessage` class is a concrete class for message model objects that represents a single user message. * The message can be a text message or media message, depending on how it is initialized. * It implements the `JSQMessageData` protocol and it contains the senderId, senderDisplayName, * and the date that the message was sent. If initialized as a media message it also contains a media attachment, * otherwise it contains the message text. */ @interface JSQMessage : NSObject /** * Returns the string identifier that uniquely identifies the user who sent the message. */ @property (copy, nonatomic, readonly) NSString *senderId; /** * Returns the display name for the user who sent the message. This value does not have to be unique. */ @property (copy, nonatomic, readonly) NSString *senderDisplayName; /** * Returns the date that the message was sent. */ @property (copy, nonatomic, readonly) NSDate *date; /** * Returns a boolean value specifying whether or not the message contains media. * If `NO`, the message contains text. If `YES`, the message contains media. * The value of this property depends on how the object was initialized. */ @property (assign, nonatomic, readonly) BOOL isMediaMessage; /** * Returns the body text of the message, or `nil` if the message is a media message. * That is, if `isMediaMessage` is equal to `YES` then this value will be `nil`. */ @property (copy, nonatomic, readonly, null_unspecified) NSString *text; /** * Returns the media item attachment of the message, or `nil` if the message is not a media message. * That is, if `isMediaMessage` is equal to `NO` then this value will be `nil`. */ @property (copy, nonatomic, readonly, null_unspecified) id media; #pragma mark - Initialization /** * Initializes and returns a message object having the given senderId, displayName, text, * and current system date. * * @param senderId The unique identifier for the user who sent the message. This value must not be `nil`. * @param displayName The display name for the user who sent the message. This value must not be `nil`. * @param text The body text of the message. This value must not be `nil`. * * @discussion Initializing a `JSQMessage` with this method will set `isMediaMessage` to `NO`. * * @return An initialized `JSQMessage` object. */ + (instancetype)messageWithSenderId:(NSString *)senderId displayName:(NSString *)displayName text:(NSString *)text; /** * Initializes and returns a message object having the given senderId, senderDisplayName, date, and text. * * @param senderId The unique identifier for the user who sent the message. This value must not be `nil`. * @param senderDisplayName The display name for the user who sent the message. This value must not be `nil`. * @param date The date that the message was sent. This value must not be `nil`. * @param text The body text of the message. This value must not be `nil`. * * @discussion Initializing a `JSQMessage` with this method will set `isMediaMessage` to `NO`. * * @return An initialized `JSQMessage` object. */ - (instancetype)initWithSenderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date text:(NSString *)text; /** * Initializes and returns a message object having the given senderId, displayName, media, * and current system date. * * @param senderId The unique identifier for the user who sent the message. This value must not be `nil`. * @param displayName The display name for the user who sent the message. This value must not be `nil`. * @param media The media data for the message. This value must not be `nil`. * * @discussion Initializing a `JSQMessage` with this method will set `isMediaMessage` to `YES`. * * @return An initialized `JSQMessage` object. */ + (instancetype)messageWithSenderId:(NSString *)senderId displayName:(NSString *)displayName media:(id)media; /** * Initializes and returns a message object having the given senderId, displayName, date, and media. * * @param senderId The unique identifier for the user who sent the message. This value must not be `nil`. * @param senderDisplayName The display name for the user who sent the message. This value must not be `nil`. * @param date The date that the message was sent. This value must not be `nil`. * @param media The media data for the message. This value must not be `nil`. * * @discussion Initializing a `JSQMessage` with this method will set `isMediaMessage` to `YES`. * * @return An initialized `JSQMessage` object. */ - (instancetype)initWithSenderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date media:(id)media; /** * Not a valid initializer. */ - (id)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessage.m ================================================ // // 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 "JSQMessage.h" @implementation JSQMessage #pragma mark - Initialization + (instancetype)messageWithSenderId:(NSString *)senderId displayName:(NSString *)displayName text:(NSString *)text { return [[self alloc] initWithSenderId:senderId senderDisplayName:displayName date:[NSDate date] text:text]; } - (instancetype)initWithSenderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date text:(NSString *)text { NSParameterAssert(text != nil); self = [self initWithSenderId:senderId senderDisplayName:senderDisplayName date:date isMedia:NO]; if (self) { _text = [text copy]; } return self; } + (instancetype)messageWithSenderId:(NSString *)senderId displayName:(NSString *)displayName media:(id)media { return [[self alloc] initWithSenderId:senderId senderDisplayName:displayName date:[NSDate date] media:media]; } - (instancetype)initWithSenderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date media:(id)media { NSParameterAssert(media != nil); self = [self initWithSenderId:senderId senderDisplayName:senderDisplayName date:date isMedia:YES]; if (self) { _media = media; } return self; } - (instancetype)initWithSenderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName date:(NSDate *)date isMedia:(BOOL)isMedia { NSParameterAssert(senderId != nil); NSParameterAssert(senderDisplayName != nil); NSParameterAssert(date != nil); self = [super init]; if (self) { _senderId = [senderId copy]; _senderDisplayName = [senderDisplayName copy]; _date = [date copy]; _isMediaMessage = isMedia; } return self; } - (NSUInteger)messageHash { return self.hash; } #pragma mark - NSObject - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[self class]]) { return NO; } JSQMessage *aMessage = (JSQMessage *)object; if (self.isMediaMessage != aMessage.isMediaMessage) { return NO; } BOOL hasEqualContent = self.isMediaMessage ? [self.media isEqual:aMessage.media] : [self.text isEqualToString:aMessage.text]; return [self.senderId isEqualToString:aMessage.senderId] && [self.senderDisplayName isEqualToString:aMessage.senderDisplayName] && ([self.date compare:aMessage.date] == NSOrderedSame) && hasEqualContent; } - (NSUInteger)hash { NSUInteger contentHash = self.isMediaMessage ? [self.media mediaHash] : self.text.hash; return self.senderId.hash ^ self.date.hash ^ contentHash; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: senderId=%@, senderDisplayName=%@, date=%@, isMediaMessage=%@, text=%@, media=%@>", [self class], self.senderId, self.senderDisplayName, self.date, @(self.isMediaMessage), self.text, self.media]; } - (id)debugQuickLookObject { return [self.media mediaView] ?: [self.media mediaPlaceholderView]; } #pragma mark - NSCoding - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { _senderId = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(senderId))]; _senderDisplayName = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(senderDisplayName))]; _date = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(date))]; _isMediaMessage = [aDecoder decodeBoolForKey:NSStringFromSelector(@selector(isMediaMessage))]; _text = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(text))]; _media = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(media))]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.senderId forKey:NSStringFromSelector(@selector(senderId))]; [aCoder encodeObject:self.senderDisplayName forKey:NSStringFromSelector(@selector(senderDisplayName))]; [aCoder encodeObject:self.date forKey:NSStringFromSelector(@selector(date))]; [aCoder encodeBool:self.isMediaMessage forKey:NSStringFromSelector(@selector(isMediaMessage))]; [aCoder encodeObject:self.text forKey:NSStringFromSelector(@selector(text))]; if ([self.media conformsToProtocol:@protocol(NSCoding)]) { [aCoder encodeObject:self.media forKey:NSStringFromSelector(@selector(media))]; } } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { if (self.isMediaMessage) { return [[[self class] allocWithZone:zone] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date media:self.media]; } return [[[self class] allocWithZone:zone] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date text:self.text]; } @end ================================================ FILE: JSQMessagesViewController/Model/JSQMessageAvatarImageDataSource.h ================================================ // // 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 #import NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessageAvatarImageDataSource` protocol defines the common interface through which * a `JSQMessagesViewController` and `JSQMessagesCollectionView` interact with avatar image model objects. * * It declares the required and optional methods that a class must implement so that instances * of that class can be display properly within a `JSQMessagesCollectionViewCell`. * * A concrete class that conforms to this protocol is provided in the library. See `JSQMessagesAvatarImage`. * * @see JSQMessagesAvatarImage. */ @protocol JSQMessageAvatarImageDataSource @required /** * @return The avatar image for a regular display state. * * @discussion You may return `nil` from this method while the image is being downloaded. */ - (nullable UIImage *)avatarImage; /** * @return The avatar image for a highlighted display state. * * @discussion You may return `nil` from this method if this does not apply. */ - (nullable UIImage *)avatarHighlightedImage; /** * @return A placeholder avatar image to be displayed if avatarImage is not yet available, or `nil`. * For example, if avatarImage needs to be downloaded, this placeholder image * will be used until avatarImage is not `nil`. * * @discussion If you do not need support for a placeholder image, that is, your images * are stored locally on the device, then you may simply return the same value as avatarImage here. * * @warning You must not return `nil` from this method. */ - (UIImage *)avatarPlaceholderImage; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessageBubbleImageDataSource.h ================================================ // // 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 #import NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessageBubbleImageDataSource` protocol defines the common interface through which * a `JSQMessagesViewController` and `JSQMessagesCollectionView` interact with * message bubble image model objects. * * It declares the required and optional methods that a class must implement so that instances * of that class can be display properly within a `JSQMessagesCollectionViewCell`. * * A concrete class that conforms to this protocol is provided in the library. See `JSQMessagesBubbleImage`. * * @see JSQMessagesBubbleImage. */ @protocol JSQMessageBubbleImageDataSource @required /** * @return The message bubble image for a regular display state. * * @warning You must not return `nil` from this method. */ - (UIImage *)messageBubbleImage; /** * @return The message bubble image for a highlighted display state. * * @warning You must not return `nil` from this method. */ - (UIImage *)messageBubbleHighlightedImage; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessageData.h ================================================ // // 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 #import "JSQMessageMediaData.h" NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessageData` protocol defines the common interface through which * a `JSQMessagesViewController` and `JSQMessagesCollectionView` interact with message model objects. * * It declares the required and optional methods that a class must implement so that instances of that class * can be displayed properly within a `JSQMessagesCollectionViewCell`. * * The class that conforms to this protocol is provided in the library. See `JSQMessage`. * * @see JSQMessage. */ @protocol JSQMessageData @required /** * @return A string identifier that uniquely identifies the user who sent the message. * * @discussion If you need to generate a unique identifier, consider using * `[[NSProcessInfo processInfo] globallyUniqueString]` * * @warning You must not return `nil` from this method. This value must be unique. */ - (NSString *)senderId; /** * @return The display name for the user who sent the message. * * @warning You must not return `nil` from this method. */ - (NSString *)senderDisplayName; /** * @return The date that the message was sent. * * @warning You must not return `nil` from this method. */ - (NSDate *)date; /** * This method is used to determine if the message data item contains text or media. * If this method returns `YES`, an instance of `JSQMessagesViewController` will ignore * the `text` method of this protocol when dequeuing a `JSQMessagesCollectionViewCell` * and only call the `media` method. * * Similarly, if this method returns `NO` then the `media` method will be ignored and * and only the `text` method will be called. * * @return A boolean value specifying whether or not this is a media message or a text message. * Return `YES` if this item is a media message, and `NO` if it is a text message. */ - (BOOL)isMediaMessage; /** * @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; @optional /** * @return The body text of the message. * * @warning You must not return `nil` from this method. */ - (NSString *)text; /** * @return The media item of the message. * * @warning You must not return `nil` from this method. */ - (id)media; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessageMediaData.h ================================================ // // 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 #import NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessageMediaData` protocol defines the common interface through which * a `JSQMessagesViewController` and `JSQMessagesCollectionView` interact with media message model objects. * * It declares the required and optional methods that a class must implement so that instances of that class * can be displayed properly within a `JSQMessagesCollectionViewCell`. * * This library provides a few concrete classes that conform to this protocol. You may use them as-is, * but they will likely require some modifications or extensions to conform to your particular data models. * These concrete media items are: `JSQPhotoMediaItem`, `JSQLocationMediaItem`, `JSQVideoMediaItem`. * * @see JSQPhotoMediaItem. * @see JSQLocationMediaItem. * @see JSQVideoMediaItem. */ @protocol JSQMessageMediaData @required /** * @return An initialized `UIView` object that represents the data for this media object. * * @discussion You may return `nil` from this method while the media data is being downloaded. */ - (nullable UIView *)mediaView; /** * @return The frame size for the mediaView when displayed in a `JSQMessagesCollectionViewCell`. * * @discussion You should return an appropriate size value to be set for the mediaView's frame * based on the contents of the view, and the frame and layout of the `JSQMessagesCollectionViewCell` * in which mediaView will be displayed. * * @warning You must return a size with non-zero, positive width and height values. */ - (CGSize)mediaViewDisplaySize; /** * @return A placeholder media view to be displayed if mediaView is not yet available, or `nil`. * For example, if mediaView will be constructed based on media data that must be downloaded, * this placeholder view will be used until mediaView is not `nil`. * * @discussion If you do not need support for a placeholder view, then you may simply return the * same value here as mediaView. Otherwise, consider using `JSQMessagesMediaPlaceholderView`. * * @warning You must not return `nil` from this method. * * @see JSQMessagesMediaPlaceholderView. */ - (UIView *)mediaPlaceholderView; /** * @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; @optional /** * @return String which identifies type of the data returned by `mediaData` method. * * @discussion If implemented, you must not return `nil` from this, as well as `copyableData`, method. * This type is frequently, but not necessarily, a UTI (Uniform Type Identifier). It identifies a * representation of the data on the pasteboard. Apps can define their own types for custom data, * however, in this case, only those apps that know of the type could understand the data written to the pasteboard. */ - (NSString *)mediaDataType; /** * @return Data object of class corresponding to type returned by `mediaDataType`. * * @discussion You should return an object that is of a class type appropriate to the representation type, * which typically is a UTI. */ - (id)mediaData; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessagesAvatarImage.h ================================================ // // 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 #import #import "JSQMessageAvatarImageDataSource.h" NS_ASSUME_NONNULL_BEGIN /** * A `JSQMessagesAvatarImage` model object represents an avatar image. * This is a concrete class that implements the `JSQMessageAvatarImageDataSource` protocol. * It contains a regular avatar image, a highlighted avatar image, and a placeholder avatar image. * * @see JSQMessagesAvatarImageFactory. */ @interface JSQMessagesAvatarImage : NSObject /** * The avatar image for a regular display state. */ @property (nonatomic, strong, nullable) UIImage *avatarImage; /** * The avatar image for a highlighted display state. */ @property (nonatomic, strong, nullable) UIImage *avatarHighlightedImage; /** * Returns the placeholder image for an avatar to display if avatarImage is `nil`. */ @property (nonatomic, strong, readonly) UIImage *avatarPlaceholderImage; /** * Initializes and returns an avatar image object having the specified image. * * @param image The image for this avatar image. This image will be used for the all of the following * properties: avatarImage, avatarHighlightedImage, avatarPlaceholderImage; * This value must not be `nil`. * * @return An initialized `JSQMessagesAvatarImage` object. */ + (instancetype)avatarWithImage:(UIImage *)image; /** * Initializes and returns an avatar image object having the specified placeholder image. * * @param placeholderImage The placeholder image for this avatar image. This value must not be `nil`. * * @return An initialized `JSQMessagesAvatarImage` object. */ + (instancetype)avatarImageWithPlaceholder:(UIImage *)placeholderImage; /** * Initializes and returns an avatar image object having the specified regular, highlighed, and placeholder images. * * @param avatarImage The avatar image for a regular display state. * @param highlightedImage The avatar image for a highlighted display state. * @param placeholderImage The placeholder image for this avatar image. This value must not be `nil`. * * @return An initialized `JSQMessagesAvatarImage` object. */ - (instancetype)initWithAvatarImage:(nullable UIImage *)avatarImage highlightedImage:(nullable UIImage *)highlightedImage placeholderImage:(UIImage *)placeholderImage NS_DESIGNATED_INITIALIZER; /** * Not a valid initializer. */ - (id)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessagesAvatarImage.m ================================================ // // 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 "JSQMessagesAvatarImage.h" @implementation JSQMessagesAvatarImage #pragma mark - Initialization + (instancetype)avatarWithImage:(UIImage *)image { NSParameterAssert(image != nil); return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:image highlightedImage:image placeholderImage:image]; } + (instancetype)avatarImageWithPlaceholder:(UIImage *)placeholderImage { return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:nil highlightedImage:nil placeholderImage:placeholderImage]; } - (instancetype)initWithAvatarImage:(UIImage *)avatarImage highlightedImage:(UIImage *)highlightedImage placeholderImage:(UIImage *)placeholderImage { NSParameterAssert(placeholderImage != nil); self = [super init]; if (self) { _avatarImage = avatarImage; _avatarHighlightedImage = highlightedImage; _avatarPlaceholderImage = placeholderImage; } return self; } #pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: avatarImage=%@, avatarHighlightedImage=%@, avatarPlaceholderImage=%@>", [self class], self.avatarImage, self.avatarHighlightedImage, self.avatarPlaceholderImage]; } - (id)debugQuickLookObject { return [[UIImageView alloc] initWithImage:self.avatarImage ?: self.avatarPlaceholderImage]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { return [[[self class] allocWithZone:zone] initWithAvatarImage:[UIImage imageWithCGImage:self.avatarImage.CGImage] highlightedImage:[UIImage imageWithCGImage:self.avatarHighlightedImage.CGImage] placeholderImage:[UIImage imageWithCGImage:self.avatarPlaceholderImage.CGImage]]; } @end ================================================ FILE: JSQMessagesViewController/Model/JSQMessagesBubbleImage.h ================================================ // // 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 #import #import "JSQMessageBubbleImageDataSource.h" NS_ASSUME_NONNULL_BEGIN /** * A `JSQMessagesBubbleImage` model object represents a message bubble image, and is immutable. * This is a concrete class that implements the `JSQMessageBubbleImageDataSource` protocol. * It contains a regular message bubble image and a highlighted message bubble image. * * @see JSQMessagesBubbleImageFactory. */ @interface JSQMessagesBubbleImage : NSObject /** * Returns the message bubble image for a regular display state. */ @property (strong, nonatomic, readonly) UIImage *messageBubbleImage; /** * Returns the message bubble image for a highlighted display state. */ @property (strong, nonatomic, readonly) UIImage *messageBubbleHighlightedImage; /** * Initializes and returns a message bubble image object having the specified regular image and highlighted image. * * @param image The regular message bubble image. This value must not be `nil`. * @param highlightedImage The highlighted message bubble image. This value must not be `nil`. * * @return An initialized `JSQMessagesBubbleImage` object. * * @see JSQMessagesBubbleImageFactory. */ - (instancetype)initWithMessageBubbleImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage NS_DESIGNATED_INITIALIZER; /** * Not a valid initializer. */ - (id)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessagesBubbleImage.m ================================================ // // 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 "JSQMessagesBubbleImage.h" @implementation JSQMessagesBubbleImage #pragma mark - Initialization - (instancetype)initWithMessageBubbleImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage { NSParameterAssert(image != nil); NSParameterAssert(highlightedImage != nil); self = [super init]; if (self) { _messageBubbleImage = image; _messageBubbleHighlightedImage = highlightedImage; } return self; } #pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: messageBubbleImage=%@, messageBubbleHighlightedImage=%@>", [self class], self.messageBubbleImage, self.messageBubbleHighlightedImage]; } - (id)debugQuickLookObject { return [[UIImageView alloc] initWithImage:self.messageBubbleImage highlightedImage:self.messageBubbleHighlightedImage]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { return [[[self class] allocWithZone:zone] initWithMessageBubbleImage:[UIImage imageWithCGImage:self.messageBubbleImage.CGImage] highlightedImage:[UIImage imageWithCGImage:self.messageBubbleHighlightedImage.CGImage]]; } @end ================================================ FILE: JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h ================================================ // // 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 #import @class JSQMessagesCollectionView; @protocol JSQMessageData; @protocol JSQMessageBubbleImageDataSource; @protocol JSQMessageAvatarImageDataSource; NS_ASSUME_NONNULL_BEGIN /** * An object that adopts the `JSQMessagesCollectionViewDataSource` protocol is responsible for providing the data and views * required by a `JSQMessagesCollectionView`. The data source object represents your app’s messaging data model * and vends information to the collection view as needed. */ @protocol JSQMessagesCollectionViewDataSource @required /** * Asks the data source for the current sender's display name, that is, the current user who is sending messages. * * @return An initialized string describing the current sender to display in a `JSQMessagesCollectionViewCell`. * * @warning You must not return `nil` from this method. This value does not need to be unique. */ - (NSString *)senderDisplayName; /** * Asks the data source for the current sender's unique identifier, that is, the current user who is sending messages. * * @return An initialized string identifier that uniquely identifies the current sender. * * @warning You must not return `nil` from this method. This value must be unique. */ - (NSString *)senderId; /** * Asks the data source for the message data that corresponds to the specified item at indexPath in the collectionView. * * @param collectionView The collection view requesting this information. * @param indexPath The index path that specifies the location of the item. * * @return An initialized object that conforms to the `JSQMessageData` protocol. You must not return `nil` from this method. */ - (id)collectionView:(JSQMessagesCollectionView *)collectionView messageDataForItemAtIndexPath:(NSIndexPath *)indexPath; /** * Notifies the data source that the item at indexPath has been deleted. * Implementations of this method should remove the item from the data source. * * @param collectionView The collection view requesting this information. * @param indexPath The index path that specifies the location of the item. */ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didDeleteMessageAtIndexPath:(NSIndexPath *)indexPath; /** * Asks the data source for the message bubble image data that corresponds to the specified message data item at indexPath in the collectionView. * * @param collectionView The collection view requesting this information. * @param indexPath The index path that specifies the location of the item. * * @return An initialized object that conforms to the `JSQMessageBubbleImageDataSource` protocol. You may return `nil` from this method if you do not * want the specified item to display a message bubble image. * * @discussion It is recommended that you utilize `JSQMessagesBubbleImageFactory` to return valid `JSQMessagesBubbleImage` objects. * However, you may provide your own data source object as long as it conforms to the `JSQMessageBubbleImageDataSource` protocol. * * @warning Note that providing your own bubble image data source objects may require additional * configuration of the collectionView layout object, specifically regarding its `messageBubbleTextViewFrameInsets` and `messageBubbleTextViewTextContainerInsets`. * * @see JSQMessagesBubbleImageFactory. * @see JSQMessagesCollectionViewFlowLayout. */ - (nullable id)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath; /** * Asks the data source for the avatar image data that corresponds to the specified message data item at indexPath in the collectionView. * * @param collectionView The collection view requesting this information. * @param indexPath The index path that specifies the location of the item. * * @return A initialized object that conforms to the `JSQMessageAvatarImageDataSource` protocol. You may return `nil` from this method if you do not want * the specified item to display an avatar. * * @discussion It is recommended that you utilize `JSQMessagesAvatarImageFactory` to return valid `JSQMessagesAvatarImage` objects. * However, you may provide your own data source object as long as it conforms to the `JSQMessageAvatarImageDataSource` protocol. * * @see JSQMessagesAvatarImageFactory. * @see JSQMessagesCollectionViewFlowLayout. */ - (nullable id)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath; @optional /** * Asks the data source for the text to display in the `cellTopLabel` for the specified * message data item at indexPath in the collectionView. * * @param collectionView The collection view requesting this information. * @param indexPath The index path that specifies the location of the item. * * @return A configured attributed string or `nil` if you do not want text displayed for the item at indexPath. * Return an attributed string with `nil` attributes to use the default attributes. * * @see JSQMessagesCollectionViewCell. */ - (nullable NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath; /** * Asks the data source for the text to display in the `messageBubbleTopLabel` for the specified * message data item at indexPath in the collectionView. * * @param collectionView The collection view requesting this information. * @param indexPath The index path that specifies the location of the item. * * @return A configured attributed string or `nil` if you do not want text displayed for the item at indexPath. * Return an attributed string with `nil` attributes to use the default attributes. * * @see JSQMessagesCollectionViewCell. */ - (nullable NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath; /** * Asks the data source for the text to display in the `cellBottomLabel` for the the specified * message data item at indexPath in the collectionView. * * @param collectionView The collection view requesting this information. * @param indexPath The index path that specifies the location of the item. * * @return A configured attributed string or `nil` if you do not want text displayed for the item at indexPath. * Return an attributed string with `nil` attributes to use the default attributes. * * @see JSQMessagesCollectionViewCell. */ - (nullable NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessagesCollectionViewDelegateFlowLayout.h ================================================ // // 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 #import @class JSQMessagesCollectionView; @class JSQMessagesCollectionViewFlowLayout; @class JSQMessagesCollectionViewCell; @class JSQMessagesLoadEarlierHeaderView; NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesCollectionViewDelegateFlowLayout` protocol defines methods that allow you to * manage additional layout information for the collection view and respond to additional actions on its items. * The methods of this protocol are all optional. */ @protocol JSQMessagesCollectionViewDelegateFlowLayout @optional /** * Asks the delegate for the height of the `cellTopLabel` for the item at the specified indexPath. * * @param collectionView The collection view object displaying the flow layout. * @param collectionViewLayout The layout object requesting the information. * @param indexPath The index path of the item. * * @return The height of the `cellTopLabel` for the item at indexPath. * * @see JSQMessagesCollectionViewCell. */ - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath; /** * Asks the delegate for the height of the `messageBubbleTopLabel` for the item at the specified indexPath. * * @param collectionView The collection view object displaying the flow layout. * @param collectionViewLayout The layout object requesting the information. * @param indexPath The index path of the item. * * @return The height of the `messageBubbleTopLabel` for the item at indexPath. * * @see JSQMessagesCollectionViewCell. */ - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath; /** * Asks the delegate for the height of the `cellBottomLabel` for the item at the specified indexPath. * * @param collectionView The collection view object displaying the flow layout. * @param collectionViewLayout The layout object requesting the information. * @param indexPath The index path of the item. * * @return The height of the `cellBottomLabel` for the item at indexPath. * * @see JSQMessagesCollectionViewCell. */ - (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath; /** * Notifies the delegate that the avatar image view at the specified indexPath did receive a tap event. * * @param collectionView The collection view object that is notifying the delegate of the tap event. * @param avatarImageView The avatar image view that was tapped. * @param indexPath The index path of the item for which the avatar was tapped. */ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapAvatarImageView:(UIImageView *)avatarImageView atIndexPath:(NSIndexPath *)indexPath; /** * Notifies the delegate that the message bubble at the specified indexPath did receive a tap event. * * @param collectionView The collection view object that is notifying the delegate of the tap event. * @param indexPath The index path of the item for which the message bubble was tapped. */ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath; /** * Notifies the delegate that the cell at the specified indexPath did receive a tap event at the specified touchLocation. * * @param collectionView The collection view object that is notifying the delegate of the tap event. * @param indexPath The index path of the item for which the message bubble was tapped. * @param touchLocation The location of the touch event in the cell's coordinate system. * * @warning This method is *only* called if position is *not* within the bounds of the cell's * avatar image view or message bubble image view. In other words, this method is *not* called when the cell's * avatar or message bubble are tapped. There are separate delegate methods for these two cases. * * @see `collectionView:didTapAvatarImageView:atIndexPath:` * @see `collectionView:didTapMessageBubbleAtIndexPath:atIndexPath:` */ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCellAtIndexPath:(NSIndexPath *)indexPath touchLocation:(CGPoint)touchLocation; /** * Notifies the delegate that the collection view's header did receive a tap event. * * @param collectionView The collection view object that is notifying the delegate of the tap event. * @param headerView The header view in the collection view. * @param sender The button that was tapped. */ - (void)collectionView:(JSQMessagesCollectionView *)collectionView header:(JSQMessagesLoadEarlierHeaderView *)headerView didTapLoadEarlierMessagesButton:(UIButton *)sender; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQMessagesViewAccessoryButtonDelegate.h ================================================ // // 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 NS_ASSUME_NONNULL_BEGIN @class JSQMessagesCollectionView; /** * The `JSQMessagesViewAccessoryButtonDelegate` protocol defines methods that allow you to * handle accessory actions for the collection view. */ @protocol JSQMessagesViewAccessoryButtonDelegate @required /** * Notifies the delegate that the accessory button at the specified indexPath did receive a tap event. * * @param messageView The collection view object that is notifying the delegate of the tap event. * @param path The index path of the item for which the accessory button was tapped. */ - (void)messageView:(JSQMessagesCollectionView *)messageView didTapAccessoryButtonAtIndexPath:(NSIndexPath *)path; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQPhotoMediaItem.h ================================================ // // 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 "JSQMediaItem.h" NS_ASSUME_NONNULL_BEGIN /** * The `JSQPhotoMediaItem` class is a concrete `JSQMediaItem` subclass that implements the `JSQMessageMediaData` protocol * and represents a photo media message. An initialized `JSQPhotoMediaItem` object can be passed * to a `JSQMediaMessage` object during its initialization to construct a valid media message object. * You may wish to subclass `JSQPhotoMediaItem` to provide additional functionality or behavior. */ @interface JSQPhotoMediaItem : JSQMediaItem /** * The image for the photo media item. The default value is `nil`. */ @property (copy, nonatomic, nullable) UIImage *image; /** * Initializes and returns a photo media item object having the given image. * * @param image The image for the photo media item. This value may be `nil`. * * @return An initialized `JSQPhotoMediaItem`. * * @discussion If the image must be dowloaded from the network, * you may initialize a `JSQPhotoMediaItem` object with a `nil` image. * Once the image has been retrieved, you can then set the image property. */ - (instancetype)initWithImage:(nullable UIImage *)image; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQPhotoMediaItem.m ================================================ // // 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 "JSQPhotoMediaItem.h" #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" #import @interface JSQPhotoMediaItem () @property (strong, nonatomic) UIImageView *cachedImageView; @end @implementation JSQPhotoMediaItem #pragma mark - Initialization - (instancetype)initWithImage:(UIImage *)image { self = [super init]; if (self) { _image = [image copy]; _cachedImageView = nil; } return self; } - (void)clearCachedMediaViews { [super clearCachedMediaViews]; _cachedImageView = nil; } #pragma mark - Setters - (void)setImage:(UIImage *)image { _image = [image copy]; _cachedImageView = nil; } - (void)setAppliesMediaViewMaskAsOutgoing:(BOOL)appliesMediaViewMaskAsOutgoing { [super setAppliesMediaViewMaskAsOutgoing:appliesMediaViewMaskAsOutgoing]; _cachedImageView = nil; } #pragma mark - JSQMessageMediaData protocol - (UIView *)mediaView { if (self.image == nil) { return nil; } if (self.cachedImageView == nil) { CGSize size = [self mediaViewDisplaySize]; UIImageView *imageView = [[UIImageView alloc] initWithImage:self.image]; imageView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height); imageView.contentMode = UIViewContentModeScaleAspectFill; imageView.clipsToBounds = YES; [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:imageView isOutgoing:self.appliesMediaViewMaskAsOutgoing]; self.cachedImageView = imageView; } return self.cachedImageView; } - (NSUInteger)mediaHash { return self.hash; } - (NSString *)mediaDataType { return (NSString *)kUTTypeJPEG; } - (id)mediaData { return UIImageJPEGRepresentation(self.image, 1); } #pragma mark - NSObject - (NSUInteger)hash { return super.hash ^ self.image.hash; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: image=%@, appliesMediaViewMaskAsOutgoing=%@>", [self class], self.image, @(self.appliesMediaViewMaskAsOutgoing)]; } #pragma mark - NSCoding - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { _image = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(image))]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeObject:self.image forKey:NSStringFromSelector(@selector(image))]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { JSQPhotoMediaItem *copy = [[JSQPhotoMediaItem allocWithZone:zone] initWithImage:self.image]; copy.appliesMediaViewMaskAsOutgoing = self.appliesMediaViewMaskAsOutgoing; return copy; } @end ================================================ FILE: JSQMessagesViewController/Model/JSQVideoMediaItem.h ================================================ // // 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 "JSQMediaItem.h" NS_ASSUME_NONNULL_BEGIN /** * The `JSQVideoMediaItem` class is a concrete `JSQMediaItem` subclass that implements the `JSQMessageMediaData` protocol * and represents a video media message. An initialized `JSQVideoMediaItem` object can be passed * to a `JSQMediaMessage` object during its initialization to construct a valid media message object. * You may wish to subclass `JSQVideoMediaItem` to provide additional functionality or behavior. */ @interface JSQVideoMediaItem : JSQMediaItem /** * The URL that identifies a video resource. */ @property (nonatomic, strong, nullable) NSURL *fileURL; /** * The thumbnail image to display for the video. */ @property (nonatomic, strong, nullable) UIImage *thumbnailImage; /** * A boolean value that specifies whether or not the video is ready to be played. * * @discussion When set to `YES`, the video is ready. When set to `NO` it is not ready. */ @property (nonatomic, assign) BOOL isReadyToPlay; /** * Initializes and returns a video media item having the given fileURL. * * @param fileURL The URL that identifies the video resource. * @param isReadyToPlay A boolean value that specifies if the video is ready to play. * * @return An initialized `JSQVideoMediaItem`. * * @discussion If the video must be downloaded from the network, * you may initialize a `JSQVideoMediaItem` with a `nil` fileURL or specify `NO` for * isReadyToPlay. Once the video has been saved to disk, or is ready to stream, you can * set the fileURL property or isReadyToPlay property, respectively. */ - (instancetype)initWithFileURL:(nullable NSURL *)fileURL isReadyToPlay:(BOOL)isReadyToPlay; /** * Initializes and returns a video media item having the given fileURL. * * @param fileURL The URL that identifies the video resource. * @param isReadyToPlay A boolean value that specifies if the video is ready to play. * @param thumbnailImage The background thumbnail image for the video. * * @return An initialized `JSQVideoMediaItem` if successful, `nil` otherwise. * * @discussion If the video must be downloaded from the network, * you may initialize a `JSQVideoMediaItem` with a `nil` fileURL or specify `NO` for * isReadyToPlay. Once the video has been saved to disk, or is ready to stream, you can * set the fileURL property or isReadyToPlay property, respectively. The background thumbnail * is optional. */ - (instancetype)initWithFileURL:(NSURL *)fileURL isReadyToPlay:(BOOL)isReadyToPlay thumbnailImage:(nullable UIImage *)thumbnailImage; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Model/JSQVideoMediaItem.m ================================================ // // 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 "JSQVideoMediaItem.h" #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" #import "JSQMessagesVideoThumbnailFactory.h" #import "UIImage+JSQMessages.h" @interface JSQVideoMediaItem () @property (strong, nonatomic) UIImageView *cachedVideoImageView; @end @implementation JSQVideoMediaItem #pragma mark - Initialization - (instancetype)initWithFileURL:(NSURL *)fileURL isReadyToPlay:(BOOL)isReadyToPlay { return [self initWithFileURL:fileURL isReadyToPlay:isReadyToPlay thumbnailImage:nil]; } - (instancetype)initWithFileURL:(NSURL *)fileURL isReadyToPlay:(BOOL)isReadyToPlay thumbnailImage:(UIImage *)thumbnailImage { self = [super init]; if (self) { _fileURL = [fileURL copy]; _isReadyToPlay = isReadyToPlay; _cachedVideoImageView = nil; _thumbnailImage = thumbnailImage; } return self; } - (void)clearCachedMediaViews { [super clearCachedMediaViews]; _cachedVideoImageView = nil; } #pragma mark - Setters - (void)setFileURL:(NSURL *)fileURL { _fileURL = [fileURL copy]; _cachedVideoImageView = nil; } - (void)setIsReadyToPlay:(BOOL)isReadyToPlay { _isReadyToPlay = isReadyToPlay; _cachedVideoImageView = nil; } - (void)setAppliesMediaViewMaskAsOutgoing:(BOOL)appliesMediaViewMaskAsOutgoing { [super setAppliesMediaViewMaskAsOutgoing:appliesMediaViewMaskAsOutgoing]; _cachedVideoImageView = nil; } #pragma mark - JSQMessageMediaData protocol - (UIView *)mediaView { if (self.fileURL == nil || !self.isReadyToPlay) { return nil; } if (self.cachedVideoImageView == nil) { CGSize size = [self mediaViewDisplaySize]; UIImage *playIcon = [[UIImage jsq_defaultPlayImage] jsq_imageMaskedWithColor:[UIColor lightGrayColor]]; UIImageView *imageView = [[UIImageView alloc] initWithImage:playIcon]; imageView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height); imageView.contentMode = UIViewContentModeCenter; imageView.clipsToBounds = YES; [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:imageView isOutgoing:self.appliesMediaViewMaskAsOutgoing]; if (self.thumbnailImage) { UIImageView *thumbnailImageView = [[UIImageView alloc] initWithImage:self.thumbnailImage]; thumbnailImageView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height); thumbnailImageView.contentMode = UIViewContentModeCenter; thumbnailImageView.clipsToBounds = YES; [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:thumbnailImageView isOutgoing:self.appliesMediaViewMaskAsOutgoing]; imageView.backgroundColor = [UIColor clearColor]; [thumbnailImageView addSubview:imageView]; self.cachedVideoImageView = thumbnailImageView; } else { imageView.backgroundColor = [UIColor blackColor]; self.cachedVideoImageView = imageView; } } return self.cachedVideoImageView; } - (NSUInteger)mediaHash { return self.hash; } #pragma mark - NSObject - (BOOL)isEqual:(id)object { if (![super isEqual:object]) { return NO; } JSQVideoMediaItem *videoItem = (JSQVideoMediaItem *)object; return [self.fileURL isEqual:videoItem.fileURL] && self.isReadyToPlay == videoItem.isReadyToPlay; } - (NSUInteger)hash { return super.hash ^ self.fileURL.hash; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: fileURL=%@, isReadyToPlay=%@, appliesMediaViewMaskAsOutgoing=%@>, thumbnailImage=%@", [self class], self.fileURL, @(self.isReadyToPlay), @(self.appliesMediaViewMaskAsOutgoing), self.thumbnailImage]; } #pragma mark - NSCoding - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { _fileURL = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(fileURL))]; _isReadyToPlay = [aDecoder decodeBoolForKey:NSStringFromSelector(@selector(isReadyToPlay))]; _thumbnailImage = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(thumbnailImage))]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeObject:self.fileURL forKey:NSStringFromSelector(@selector(fileURL))]; [aCoder encodeBool:self.isReadyToPlay forKey:NSStringFromSelector(@selector(isReadyToPlay))]; [aCoder encodeObject:self.thumbnailImage forKey:NSStringFromSelector(@selector(thumbnailImage))]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { JSQVideoMediaItem *copy = [[[self class] allocWithZone:zone] initWithFileURL:self.fileURL isReadyToPlay:self.isReadyToPlay thumbnailImage:self.thumbnailImage]; copy.appliesMediaViewMaskAsOutgoing = self.appliesMediaViewMaskAsOutgoing; return copy; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCellTextView.h ================================================ // // 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 /** * `JSQMessagesCellTextView` is a subclass of `UITextView` that is used to display text * in a `JSQMessagesCollectionViewCell`. */ @interface JSQMessagesCellTextView : UITextView @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCellTextView.m ================================================ // // 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 "JSQMessagesCellTextView.h" @implementation JSQMessagesCellTextView - (void)awakeFromNib { [super awakeFromNib]; self.textColor = [UIColor whiteColor]; self.editable = NO; self.selectable = YES; self.userInteractionEnabled = YES; self.dataDetectorTypes = UIDataDetectorTypeNone; self.showsHorizontalScrollIndicator = NO; self.showsVerticalScrollIndicator = NO; self.scrollEnabled = NO; self.backgroundColor = [UIColor clearColor]; self.contentInset = UIEdgeInsetsZero; self.scrollIndicatorInsets = UIEdgeInsetsZero; self.contentOffset = CGPointZero; self.textContainerInset = UIEdgeInsetsZero; self.textContainer.lineFragmentPadding = 0; self.linkTextAttributes = @{ NSForegroundColorAttributeName : [UIColor whiteColor], NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) }; } - (void)setSelectedRange:(NSRange)selectedRange { // attempt to prevent selecting text [super setSelectedRange:NSMakeRange(NSNotFound, 0)]; } - (NSRange)selectedRange { // attempt to prevent selecting text return NSMakeRange(NSNotFound, NSNotFound); } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { // ignore double-tap to prevent copy/define/etc. menu from showing if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { UITapGestureRecognizer *tap = (UITapGestureRecognizer *)gestureRecognizer; if (tap.numberOfTapsRequired == 2) { return NO; } } return YES; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { // ignore double-tap to prevent copy/define/etc. menu from showing if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { UITapGestureRecognizer *tap = (UITapGestureRecognizer *)gestureRecognizer; if (tap.numberOfTapsRequired == 2) { return NO; } } return YES; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionView.h ================================================ // // 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 #import "JSQMessagesCollectionViewFlowLayout.h" #import "JSQMessagesCollectionViewDelegateFlowLayout.h" #import "JSQMessagesCollectionViewDataSource.h" #import "JSQMessagesCollectionViewCell.h" @class JSQMessagesTypingIndicatorFooterView; @class JSQMessagesLoadEarlierHeaderView; @protocol JSQMessagesViewAccessoryButtonDelegate; NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesCollectionView` class manages an ordered collection of message data items and presents * them using a specialized layout for messages. */ @interface JSQMessagesCollectionView : UICollectionView /** * The object that provides the data for the collection view. * The data source must adopt the `JSQMessagesCollectionViewDataSource` protocol. */ @property (weak, nonatomic, nullable) id dataSource; /** * The object that acts as the delegate of the collection view. * The delegate must adopt the `JSQMessagesCollectionViewDelegateFlowLayout` protocol. */ @property (weak, nonatomic, nullable) id delegate; /** * The object that handles accessory actions for the collection view. * It must adopt the `JSQMessagesViewAccessoryButtonDelegate` protocol. */ @property (weak, nonatomic, nullable) id accessoryDelegate; /** * The layout used to organize the collection view’s items. */ @property (strong, nonatomic) JSQMessagesCollectionViewFlowLayout *collectionViewLayout; /** * Specifies whether the typing indicator displays on the left or right side of the collection view * when shown. That is, whether it displays for an "incoming" or "outgoing" message. * The default value is `YES`, meaning that the typing indicator will display on the left side of the * collection view for incoming messages. * * @discussion If your `JSQMessagesViewController` subclass displays messages for right-to-left * languages, such as Arabic, set this property to `NO`. * */ @property (assign, nonatomic) BOOL typingIndicatorDisplaysOnLeft; /** * The color of the typing indicator message bubble. The default value is a light gray color. */ @property (strong, nonatomic) UIColor *typingIndicatorMessageBubbleColor; /** * The color of the typing indicator ellipsis. The default value is a dark gray color. */ @property (strong, nonatomic) UIColor *typingIndicatorEllipsisColor; /** * The color of the text in the load earlier messages header. The default value is a bright blue color. */ @property (strong, nonatomic) UIColor *loadEarlierMessagesHeaderTextColor; /** * Returns a `JSQMessagesTypingIndicatorFooterView` object for the specified index path * that is configured using the collection view's properties: * typingIndicatorDisplaysOnLeft, typingIndicatorMessageBubbleColor, typingIndicatorEllipsisColor. * * @param indexPath The index path specifying the location of the supplementary view in the collection view. This value must not be `nil`. * * @return A valid `JSQMessagesTypingIndicatorFooterView` object. */ - (JSQMessagesTypingIndicatorFooterView *)dequeueTypingIndicatorFooterViewForIndexPath:(NSIndexPath *)indexPath; /** * Returns a `JSQMessagesLoadEarlierHeaderView` object for the specified index path * that is configured using the collection view's loadEarlierMessagesHeaderTextColor property. * * @param indexPath The index path specifying the location of the supplementary view in the collection view. This value must not be `nil`. * * @return A valid `JSQMessagesLoadEarlierHeaderView` object. */ - (JSQMessagesLoadEarlierHeaderView *)dequeueLoadEarlierMessagesViewHeaderForIndexPath:(NSIndexPath *)indexPath; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionView.m ================================================ // // 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 "JSQMessagesCollectionView.h" #import "JSQMessagesViewAccessoryButtonDelegate.h" #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" #import "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesLoadEarlierHeaderView.h" #import "UIColor+JSQMessages.h" @interface JSQMessagesCollectionView () - (void)jsq_configureCollectionView; @end @implementation JSQMessagesCollectionView @dynamic dataSource; @dynamic delegate; @dynamic collectionViewLayout; #pragma mark - Initialization - (void)jsq_configureCollectionView { [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.backgroundColor = [UIColor whiteColor]; self.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; self.alwaysBounceVertical = YES; self.bounces = YES; [self registerNib:[JSQMessagesCollectionViewCellIncoming nib] forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellIncoming cellReuseIdentifier]]; [self registerNib:[JSQMessagesCollectionViewCellOutgoing nib] forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]]; [self registerNib:[JSQMessagesCollectionViewCellIncoming nib] forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellIncoming mediaCellReuseIdentifier]]; [self registerNib:[JSQMessagesCollectionViewCellOutgoing nib] forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellOutgoing mediaCellReuseIdentifier]]; [self registerNib:[JSQMessagesTypingIndicatorFooterView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[JSQMessagesTypingIndicatorFooterView footerReuseIdentifier]]; [self registerNib:[JSQMessagesLoadEarlierHeaderView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[JSQMessagesLoadEarlierHeaderView headerReuseIdentifier]]; _typingIndicatorDisplaysOnLeft = YES; _typingIndicatorMessageBubbleColor = [UIColor jsq_messageBubbleLightGrayColor]; _typingIndicatorEllipsisColor = [_typingIndicatorMessageBubbleColor jsq_colorByDarkeningColorWithValue:0.3f]; _loadEarlierMessagesHeaderTextColor = [UIColor jsq_messageBubbleBlueColor]; } - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { self = [super initWithFrame:frame collectionViewLayout:layout]; if (self) { [self jsq_configureCollectionView]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self jsq_configureCollectionView]; } #pragma mark - Typing indicator - (JSQMessagesTypingIndicatorFooterView *)dequeueTypingIndicatorFooterViewForIndexPath:(NSIndexPath *)indexPath { JSQMessagesTypingIndicatorFooterView *footerView = [super dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[JSQMessagesTypingIndicatorFooterView footerReuseIdentifier] forIndexPath:indexPath]; [footerView configureWithEllipsisColor:self.typingIndicatorEllipsisColor messageBubbleColor:self.typingIndicatorMessageBubbleColor animated:YES shouldDisplayOnLeft:self.typingIndicatorDisplaysOnLeft forCollectionView:self]; return footerView; } #pragma mark - Load earlier messages header - (JSQMessagesLoadEarlierHeaderView *)dequeueLoadEarlierMessagesViewHeaderForIndexPath:(NSIndexPath *)indexPath { JSQMessagesLoadEarlierHeaderView *headerView = [super dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[JSQMessagesLoadEarlierHeaderView headerReuseIdentifier] forIndexPath:indexPath]; headerView.loadButton.tintColor = self.loadEarlierMessagesHeaderTextColor; headerView.delegate = self; return headerView; } #pragma mark - Load earlier messages header delegate - (void)headerView:(JSQMessagesLoadEarlierHeaderView *)headerView didPressLoadButton:(UIButton *)sender { if ([self.delegate respondsToSelector:@selector(collectionView:header:didTapLoadEarlierMessagesButton:)]) { [self.delegate collectionView:self header:headerView didTapLoadEarlierMessagesButton:sender]; } } #pragma mark - Messages collection view cell delegate - (void)messagesCollectionViewCellDidTapAvatar:(JSQMessagesCollectionViewCell *)cell { NSIndexPath *indexPath = [self indexPathForCell:cell]; if (indexPath == nil) { return; } [self.delegate collectionView:self didTapAvatarImageView:cell.avatarImageView atIndexPath:indexPath]; } - (void)messagesCollectionViewCellDidTapMessageBubble:(JSQMessagesCollectionViewCell *)cell { NSIndexPath *indexPath = [self indexPathForCell:cell]; if (indexPath == nil) { return; } [self.delegate collectionView:self didTapMessageBubbleAtIndexPath:indexPath]; } - (void)messagesCollectionViewCellDidTapCell:(JSQMessagesCollectionViewCell *)cell atPosition:(CGPoint)position { NSIndexPath *indexPath = [self indexPathForCell:cell]; if (indexPath == nil) { return; } [self.delegate collectionView:self didTapCellAtIndexPath:indexPath touchLocation:position]; } - (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]; } - (void)messagesCollectionViewCellDidTapAccessoryButton:(JSQMessagesCollectionViewCell *)cell { NSIndexPath *indexPath = [self indexPathForCell:cell]; if (indexPath == nil) { return; } [self.accessoryDelegate messageView:self didTapAccessoryButtonAtIndexPath:indexPath]; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h ================================================ // // 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 #import "JSQMessagesLabel.h" #import "JSQMessagesCellTextView.h" @class JSQMessagesCollectionViewCell; NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesCollectionViewCellDelegate` protocol defines methods that allow you to manage * additional interactions within the collection view cell. */ @protocol JSQMessagesCollectionViewCellDelegate @required /** * Tells the delegate that the avatarImageView of the cell has been tapped. * * @param cell The cell that received the tap touch event. */ - (void)messagesCollectionViewCellDidTapAvatar:(JSQMessagesCollectionViewCell *)cell; /** * Tells the delegate that the message bubble of the cell has been tapped. * * @param cell The cell that received the tap touch event. */ - (void)messagesCollectionViewCellDidTapMessageBubble:(JSQMessagesCollectionViewCell *)cell; /** * Tells the delegate that the cell has been tapped at the point specified by position. * * @param cell The cell that received the tap touch event. * @param position The location of the received touch in the cell's coordinate system. * * @discussion This method is *only* called if position is *not* within the bounds of the cell's * avatar image view or message bubble image view. In other words, this method is *not* called when the cell's * avatar or message bubble are tapped. * * @see `messagesCollectionViewCellDidTapAvatar:` * @see `messagesCollectionViewCellDidTapMessageBubble:` */ - (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; /** * Tells the delegate that the accessory button of the cell has been tapped. * * @param cell The cell that the accessory button belongs to. */ - (void)messagesCollectionViewCellDidTapAccessoryButton:(JSQMessagesCollectionViewCell *)cell; @end /** * The `JSQMessagesCollectionViewCell` is an abstract base class that presents the content for * a single message data item when that item is within the collection view’s visible bounds. * The 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. * * @see JSQMessagesCollectionViewCellIncoming. * @see JSQMessagesCollectionViewCellOutgoing. */ @interface JSQMessagesCollectionViewCell : UICollectionViewCell /** * The object that acts as the delegate for the cell. */ @property (weak, nonatomic, nullable) id delegate; /** * Returns the label that is pinned to the top of the cell. * This label is most commonly used to display message timestamps. */ @property (weak, nonatomic, readonly, nullable) JSQMessagesLabel *cellTopLabel; /** * Returns the label that is pinned just above the messageBubbleImageView, and below the cellTopLabel. * This label is most commonly used to display the message sender. */ @property (weak, nonatomic, readonly, nullable) JSQMessagesLabel *messageBubbleTopLabel; /** * Returns the label that is pinned to the bottom of the cell. * This label is most commonly used to display message delivery status. */ @property (weak, nonatomic, readonly, nullable) JSQMessagesLabel *cellBottomLabel; /** * Returns the text view of the cell. This text view contains the message body text. * * @warning If mediaView returns a non-nil view, then this value will be `nil`. */ @property (weak, nonatomic, readonly, nullable) JSQMessagesCellTextView *textView; /** * 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`. */ @property (weak, nonatomic, readonly, nullable) UIImageView *messageBubbleImageView; /** * Returns the message bubble container view of the cell. This view is the superview of * the cell's textView and messageBubbleImageView. * * @discussion You may customize the cell by adding custom views to this container view. * 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. * Doing so could result in unexpected behavior. */ @property (weak, nonatomic, readonly, nullable) UIView *messageBubbleContainerView; /** * Returns the avatar image view of the cell that is responsible for displaying avatar images. */ @property (weak, nonatomic, readonly, nullable) UIImageView *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:` * * @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. * Doing so could result in unexpected behavior. */ @property (weak, nonatomic, readonly, nullable) UIView *avatarContainerView; /** * Returns the accessory button of the cell. */ @property (weak, nonatomic, readonly, nullable) UIButton *accessoryButton; /** * The media view of the cell. This view displays the contents of a media message. * * @warning If this value is non-nil, then textView and messageBubbleImageView will both be `nil`. */ @property (weak, nonatomic, nullable) UIView *mediaView; /** * Returns the underlying gesture recognizer for tap gestures in the avatarImageView of the cell. * This gesture handles the tap event for the avatarImageView and notifies the cell's delegate. */ @property (weak, nonatomic, readonly, nullable) UITapGestureRecognizer *tapGestureRecognizer; #pragma mark - Class methods /** * Returns the `UINib` object initialized for the cell. * * @return The initialized `UINib` object. */ + (UINib *)nib; /** * Returns the default string used to identify a reusable cell for text message items. * * @return The string used to identify a reusable cell. */ + (NSString *)cellReuseIdentifier; /** * Returns the default string used to identify a reusable cell for media message items. * * @return The string used to identify a reusable cell. */ + (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 NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m ================================================ // // 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 "JSQMessagesCollectionViewCell.h" #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" #import "JSQMessagesCollectionViewLayoutAttributes.h" #import "UIView+JSQMessages.h" #import "UIImage+JSQMessages.h" static NSMutableSet *jsqMessagesCollectionViewCellActions = nil; @interface JSQMessagesCollectionViewCell () @property (weak, nonatomic) IBOutlet JSQMessagesLabel *cellTopLabel; @property (weak, nonatomic) IBOutlet JSQMessagesLabel *messageBubbleTopLabel; @property (weak, nonatomic) IBOutlet JSQMessagesLabel *cellBottomLabel; @property (weak, nonatomic) IBOutlet UIView *messageBubbleContainerView; @property (weak, nonatomic) IBOutlet UIImageView *messageBubbleImageView; @property (weak, nonatomic) IBOutlet JSQMessagesCellTextView *textView; @property (weak, nonatomic) IBOutlet UIImageView *avatarImageView; @property (weak, nonatomic) IBOutlet UIView *avatarContainerView; @property (weak, nonatomic) IBOutlet UIButton *accessoryButton; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageBubbleContainerWidthConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewTopVerticalSpaceConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewBottomVerticalSpaceConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewAvatarHorizontalSpaceConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewMarginHorizontalSpaceConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *cellTopLabelHeightConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageBubbleTopLabelHeightConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *cellBottomLabelHeightConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *avatarContainerViewWidthConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *avatarContainerViewHeightConstraint; @property (assign, nonatomic) UIEdgeInsets textViewFrameInsets; @property (assign, nonatomic) CGSize avatarViewSize; @property (weak, nonatomic, readwrite) UITapGestureRecognizer *tapGestureRecognizer; - (void)jsq_handleTapGesture:(UITapGestureRecognizer *)tap; - (void)jsq_updateConstraint:(NSLayoutConstraint *)constraint withConstant:(CGFloat)constant; @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]]]; } + (NSString *)cellReuseIdentifier { return NSStringFromClass([self class]); } + (NSString *)mediaCellReuseIdentifier { 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.isAccessibilityElement = YES; self.backgroundColor = [UIColor whiteColor]; self.avatarViewSize = CGSizeZero; UIFont *topLabelFont = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; self.cellTopLabel.textAlignment = NSTextAlignmentCenter; self.cellTopLabel.font = topLabelFont; self.cellTopLabel.textColor = [UIColor lightGrayColor]; self.cellTopLabel.numberOfLines = 0; UIFont *messageBubbleTopLabelFont = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; self.messageBubbleTopLabel.font = messageBubbleTopLabelFont; self.messageBubbleTopLabel.textColor = [UIColor lightGrayColor]; self.messageBubbleTopLabel.numberOfLines = 0; UIFont *bottomLabelFont = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2]; self.cellBottomLabel.font = bottomLabelFont; self.cellBottomLabel.textColor = [UIColor lightGrayColor]; self.cellBottomLabel.numberOfLines = 0; [self configureAccessoryButton]; self.cellTopLabelHeightConstraint.constant = topLabelFont.pointSize; self.messageBubbleTopLabelHeightConstraint.constant = messageBubbleTopLabelFont.pointSize; self.cellBottomLabelHeightConstraint.constant = bottomLabelFont.pointSize; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(jsq_handleTapGesture:)]; [self addGestureRecognizer:tap]; self.tapGestureRecognizer = tap; } - (void)configureAccessoryButton { UIColor *tintColor = [UIColor lightGrayColor]; UIImage *shareActionImage = [[UIImage jsq_shareActionImage] jsq_imageMaskedWithColor:tintColor]; [self.accessoryButton setImage:shareActionImage forState:UIControlStateNormal]; } - (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; } #pragma mark - Collection view cell - (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; self.accessoryButton.hidden = YES; } - (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; } else if ([self isKindOfClass:[JSQMessagesCollectionViewCellOutgoing class]]) { self.avatarViewSize = customAttributes.outgoingAvatarViewSize; } } - (void)setHighlighted:(BOOL)highlighted { [super setHighlighted:highlighted]; self.avatarImageView.highlighted = highlighted; self.messageBubbleImageView.highlighted = highlighted; } - (void)setSelected:(BOOL)selected { [super setSelected:selected]; self.avatarImageView.highlighted = selected; self.messageBubbleImageView.highlighted = selected; } #pragma mark - Menu actions - (BOOL)respondsToSelector:(SEL)aSelector { if ([jsqMessagesCollectionViewCellActions containsObject:NSStringFromSelector(aSelector)]) { return YES; } return [super respondsToSelector:aSelector]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // do nothing } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { // do nothing } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // do nothing } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { // do nothing } - (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; } - (void)setAvatarViewSize:(CGSize)avatarViewSize { if (CGSizeEqualToSize(avatarViewSize, self.avatarViewSize)) { return; } [self jsq_updateConstraint:self.avatarContainerViewWidthConstraint withConstant:avatarViewSize.width]; [self jsq_updateConstraint:self.avatarContainerViewHeightConstraint withConstant:avatarViewSize.height]; } - (void)setTextViewFrameInsets:(UIEdgeInsets)textViewFrameInsets { 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]; [self jsq_updateConstraint:self.textViewMarginHorizontalSpaceConstraint withConstant:textViewFrameInsets.left]; } - (void)setMediaView:(UIView *)mediaView { [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 dispatch_async(dispatch_get_main_queue(), ^{ [self.messageBubbleContainerView.subviews enumerateObjectsUsingBlock:^(UIView *subview, NSUInteger index, BOOL *stop) { if (subview != _mediaView) { [subview removeFromSuperview]; } }]; }); } #pragma mark - Getters - (CGSize)avatarViewSize { return CGSizeMake(self.avatarContainerViewWidthConstraint.constant, self.avatarContainerViewHeightConstraint.constant); } - (UIEdgeInsets)textViewFrameInsets { return UIEdgeInsetsMake(self.textViewTopVerticalSpaceConstraint.constant, self.textViewMarginHorizontalSpaceConstraint.constant, self.textViewBottomVerticalSpaceConstraint.constant, self.textViewAvatarHorizontalSpaceConstraint.constant); } #pragma mark - Utilities - (void)jsq_updateConstraint:(NSLayoutConstraint *)constraint withConstant:(CGFloat)constant { if (constraint.constant == constant) { return; } constraint.constant = constant; } #pragma mark - Gesture recognizers - (void)jsq_handleTapGesture:(UITapGestureRecognizer *)tap { CGPoint touchPt = [tap locationInView:self]; if (CGRectContainsPoint(self.avatarContainerView.frame, touchPt)) { [self.delegate messagesCollectionViewCellDidTapAvatar:self]; } else if (CGRectContainsPoint(self.messageBubbleContainerView.frame, touchPt)) { [self.delegate messagesCollectionViewCellDidTapMessageBubble:self]; } else { [self.delegate messagesCollectionViewCellDidTapCell:self atPosition:touchPt]; } } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { CGPoint touchPt = [touch locationInView:self]; if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { return CGRectContainsPoint(self.messageBubbleContainerView.frame, touchPt); } return NO; } - (IBAction)didTapAccessoryButton:(UIButton *)accessoryButton { [self.delegate messagesCollectionViewCellDidTapAccessoryButton:self]; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.h ================================================ // // 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 "JSQMessagesCollectionViewCell.h" /** * A `JSQMessagesCollectionViewCellIncoming` object is a concrete instance * of `JSQMessagesCollectionViewCell` that represents an incoming message data item. */ @interface JSQMessagesCollectionViewCellIncoming : JSQMessagesCollectionViewCell @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.m ================================================ // // 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 "JSQMessagesCollectionViewCellIncoming.h" @implementation JSQMessagesCollectionViewCellIncoming #pragma mark - Overrides - (void)awakeFromNib { [super awakeFromNib]; self.messageBubbleTopLabel.textAlignment = NSTextAlignmentLeft; self.cellBottomLabel.textAlignment = NSTextAlignmentLeft; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib ================================================ ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.h ================================================ // // 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 "JSQMessagesCollectionViewCell.h" /** * A `JSQMessagesCollectionViewCellOutgoing` object is a concrete instance * of `JSQMessagesCollectionViewCell` that represents an outgoing message data item. */ @interface JSQMessagesCollectionViewCellOutgoing : JSQMessagesCollectionViewCell @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.m ================================================ // // 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 "JSQMessagesCollectionViewCellOutgoing.h" @implementation JSQMessagesCollectionViewCellOutgoing #pragma mark - Overrides - (void)awakeFromNib { [super awakeFromNib]; self.messageBubbleTopLabel.textAlignment = NSTextAlignmentRight; self.cellBottomLabel.textAlignment = NSTextAlignmentRight; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib ================================================ ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesComposerTextView.h ================================================ // // 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 @class JSQMessagesComposerTextView; NS_ASSUME_NONNULL_BEGIN /** * A delegate object used to notify the receiver of paste events from a `JSQMessagesComposerTextView`. */ @protocol JSQMessagesComposerTextViewPasteDelegate /** * Asks the delegate whether or not the `textView` should use the original implementation of `-[UITextView paste]`. * * @discussion Use this delegate method to implement custom pasting behavior. * You should return `NO` when you want to handle pasting. * Return `YES` to defer functionality to the `textView`. */ - (BOOL)composerTextView:(JSQMessagesComposerTextView *)textView shouldPasteWithSender:(id)sender; @end /** * An instance of `JSQMessagesComposerTextView` is a subclass of `UITextView` that is styled and used * for composing messages in a `JSQMessagesViewController`. It is a subview of a `JSQMessagesToolbarContentView`. */ @interface JSQMessagesComposerTextView : UITextView /** * The text to be displayed when the text view is empty. The default value is `nil`. */ @property (copy, nonatomic, nullable) NSString *placeHolder; /** * The color of the place holder text. The default value is `[UIColor lightGrayColor]`. */ @property (strong, nonatomic) UIColor *placeHolderTextColor; /** * The insets to be used when the placeholder is drawn. The default value is `UIEdgeInsets(5.0, 7.0, 5.0, 7.0)`. */ @property (assign, nonatomic) UIEdgeInsets placeHolderInsets; /** * The object that acts as the paste delegate of the text view. */ @property (weak, nonatomic, nullable) id pasteDelegate; /** * Determines whether or not the text view contains text after trimming white space * from the front and back of its string. * * @return `YES` if the text view contains text, `NO` otherwise. */ - (BOOL)hasText; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesComposerTextView.m ================================================ // // 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 "JSQMessagesComposerTextView.h" #import #import "NSString+JSQMessages.h" @interface JSQMessagesComposerTextView () @property (nonatomic, weak) NSLayoutConstraint *heightConstraint; @property (nonatomic, weak) NSLayoutConstraint *minHeightConstraint; @property (nonatomic, weak) NSLayoutConstraint *maxHeightConstraint; @end @implementation JSQMessagesComposerTextView #pragma mark - Initialization - (void)jsq_configureTextView { [self setTranslatesAutoresizingMaskIntoConstraints:NO]; CGFloat cornerRadius = 6.0f; self.backgroundColor = [UIColor whiteColor]; self.layer.borderWidth = 0.5f; self.layer.borderColor = [UIColor lightGrayColor].CGColor; self.layer.cornerRadius = cornerRadius; self.scrollIndicatorInsets = UIEdgeInsetsMake(cornerRadius, 0.0f, cornerRadius, 0.0f); self.textContainerInset = UIEdgeInsetsMake(4.0f, 2.0f, 4.0f, 2.0f); self.contentInset = UIEdgeInsetsMake(1.0f, 0.0f, 1.0f, 0.0f); self.scrollEnabled = YES; self.scrollsToTop = NO; self.userInteractionEnabled = YES; self.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; self.textColor = [UIColor blackColor]; self.textAlignment = NSTextAlignmentNatural; self.contentMode = UIViewContentModeRedraw; self.dataDetectorTypes = UIDataDetectorTypeNone; self.keyboardAppearance = UIKeyboardAppearanceDefault; self.keyboardType = UIKeyboardTypeDefault; self.returnKeyType = UIReturnKeyDefault; self.text = nil; _placeHolder = nil; _placeHolderTextColor = [UIColor lightGrayColor]; _placeHolderInsets = UIEdgeInsetsMake(5.0, 7.0, 5.0, 7.0); [self associateConstraints]; [self jsq_addTextViewNotificationObservers]; } - (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer { self = [super initWithFrame:frame textContainer:textContainer]; if (self) { [self jsq_configureTextView]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self jsq_configureTextView]; } - (void)dealloc { [self jsq_removeTextViewNotificationObservers]; } // TODO: we should just set these from the xib - (void)associateConstraints { // iterate through all text view's constraints and identify // height, max height and min height constraints. for (NSLayoutConstraint *constraint in self.constraints) { if (constraint.firstAttribute == NSLayoutAttributeHeight) { if (constraint.relation == NSLayoutRelationEqual) { self.heightConstraint = constraint; } else if (constraint.relation == NSLayoutRelationLessThanOrEqual) { self.maxHeightConstraint = constraint; } else if (constraint.relation == NSLayoutRelationGreaterThanOrEqual) { self.minHeightConstraint = constraint; } } } } - (void)layoutSubviews { [super layoutSubviews]; // calculate size needed for the text to be visible without scrolling CGSize sizeThatFits = [self sizeThatFits:self.frame.size]; float newHeight = sizeThatFits.height; // if there is any minimal height constraint set, make sure we consider that if (self.maxHeightConstraint) { newHeight = MIN(newHeight, self.maxHeightConstraint.constant); } // if there is any maximal height constraint set, make sure we consider that if (self.minHeightConstraint) { newHeight = MAX(newHeight, self.minHeightConstraint.constant); } // update the height constraint self.heightConstraint.constant = newHeight; } #pragma mark - Composer text view - (BOOL)hasText { return ([[self.text jsq_stringByTrimingWhitespace] length] > 0); } #pragma mark - Setters - (void)setPlaceHolder:(NSString *)placeHolder { if ([placeHolder isEqualToString:_placeHolder]) { return; } _placeHolder = [placeHolder copy]; [self setNeedsDisplay]; } - (void)setPlaceHolderTextColor:(UIColor *)placeHolderTextColor { if ([placeHolderTextColor isEqual:_placeHolderTextColor]) { return; } _placeHolderTextColor = placeHolderTextColor; [self setNeedsDisplay]; } - (void)setPlaceHolderInsets:(UIEdgeInsets)placeHolderInsets { if (UIEdgeInsetsEqualToEdgeInsets(placeHolderInsets, _placeHolderInsets)) { return; } _placeHolderInsets = placeHolderInsets; [self setNeedsDisplay]; } #pragma mark - UITextView overrides - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; if (self.contentSize.height <= self.bounds.size.height + 1){ self.contentOffset = CGPointZero; // Fix wrong contentOfset } } - (void)setText:(NSString *)text { [super setText:text]; [self setNeedsDisplay]; } - (void)setAttributedText:(NSAttributedString *)attributedText { [super setAttributedText:attributedText]; [self setNeedsDisplay]; } - (void)setFont:(UIFont *)font { [super setFont:font]; [self setNeedsDisplay]; } - (void)setTextAlignment:(NSTextAlignment)textAlignment { [super setTextAlignment:textAlignment]; [self setNeedsDisplay]; } - (void)paste:(id)sender { if (!self.pasteDelegate || [self.pasteDelegate composerTextView:self shouldPasteWithSender:sender]) { [super paste:sender]; } } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { [super drawRect:rect]; if ([self.text length] == 0 && self.placeHolder) { [self.placeHolderTextColor set]; [self.placeHolder drawInRect:UIEdgeInsetsInsetRect(rect, self.placeHolderInsets) withAttributes:[self jsq_placeholderTextAttributes]]; } } #pragma mark - Notifications - (void)jsq_addTextViewNotificationObservers { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jsq_didReceiveTextViewNotification:) name:UITextViewTextDidChangeNotification object:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jsq_didReceiveTextViewNotification:) name:UITextViewTextDidBeginEditingNotification object:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jsq_didReceiveTextViewNotification:) name:UITextViewTextDidEndEditingNotification object:self]; } - (void)jsq_removeTextViewNotificationObservers { [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidBeginEditingNotification object:self]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidEndEditingNotification object:self]; } - (void)jsq_didReceiveTextViewNotification:(NSNotification *)notification { [self setNeedsDisplay]; } #pragma mark - Utilities - (NSDictionary *)jsq_placeholderTextAttributes { NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail; paragraphStyle.alignment = self.textAlignment; return @{ NSFontAttributeName : self.font, NSForegroundColorAttributeName : self.placeHolderTextColor, NSParagraphStyleAttributeName : paragraphStyle }; } #pragma mark - UIMenuController - (BOOL)canBecomeFirstResponder { return [super canBecomeFirstResponder]; } - (BOOL)becomeFirstResponder { return [super becomeFirstResponder]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { [UIMenuController sharedMenuController].menuItems = nil; return [super canPerformAction:action withSender:sender]; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesInputToolbar.h ================================================ // // 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 #import #import "JSQMessagesToolbarContentView.h" @class JSQMessagesInputToolbar; NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, JSQMessagesInputSendButtonLocation) { JSQMessagesInputSendButtonLocationNone, JSQMessagesInputSendButtonLocationRight, JSQMessagesInputSendButtonLocationLeft }; /** * The `JSQMessagesInputToolbarDelegate` protocol defines methods for interacting with * a `JSQMessagesInputToolbar` object. */ @protocol JSQMessagesInputToolbarDelegate @required /** * Tells the delegate that the toolbar's `rightBarButtonItem` has been pressed. * * @param toolbar The object representing the toolbar sending this information. * @param sender The button that received the touch event. */ - (void)messagesInputToolbar:(JSQMessagesInputToolbar *)toolbar didPressRightBarButton:(UIButton *)sender; /** * Tells the delegate that the toolbar's `leftBarButtonItem` has been pressed. * * @param toolbar The object representing the toolbar sending this information. * @param sender The button that received the touch event. */ - (void)messagesInputToolbar:(JSQMessagesInputToolbar *)toolbar didPressLeftBarButton:(UIButton *)sender; @end /** * 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. */ @interface JSQMessagesInputToolbar : UIToolbar /** * The object that acts as the delegate of the toolbar. */ @property (weak, nonatomic, nullable) id delegate; /** * Returns the content view of the toolbar. This view contains all subviews of the toolbar. */ @property (weak, nonatomic, readonly, nullable) JSQMessagesToolbarContentView *contentView; /** * Indicates the location of the send button in the toolbar. * * @discussion The default value is `JSQMessagesInputSendButtonLocationRight`, which indicates that the send button is the right-most subview of * the toolbar's `contentView`. Set to `JSQMessagesInputSendButtonLocationLeft` to specify that the send button is on the left. Set to 'JSQMessagesInputSendButtonLocationNone' if there is no send button or if you want to take control of the send button actions. This * property is used to determine which touch events correspond to which actions. * * @warning Note, this property *does not* change the positions of buttons in the toolbar's content view. * It only specifies whether the `rightBarButtonItem` or the `leftBarButtonItem` is the send button or there is no send button. * The other button then acts as the accessory button. */ @property (assign, nonatomic) JSQMessagesInputSendButtonLocation sendButtonLocation; /** * Specify if the send button should be enabled automatically when the `textView` contains text. * The default value is `YES`. * * @discussion If `YES`, the send button will be enabled if the `textView` contains text. Otherwise, * you are responsible for determining when to enable/disable the send button. */ @property (assign, nonatomic) BOOL enablesSendButtonAutomatically; /** * 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; /** * Loads the content view for the toolbar. * * @discussion Override this method to provide a custom content view for the toolbar. * * @return An initialized `JSQMessagesToolbarContentView`. */ - (JSQMessagesToolbarContentView *)loadToolbarContentView; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesInputToolbar.m ================================================ // // 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 "JSQMessagesInputToolbar.h" #import "JSQMessagesComposerTextView.h" #import "JSQMessagesToolbarButtonFactory.h" #import "UIColor+JSQMessages.h" #import "UIImage+JSQMessages.h" #import "UIView+JSQMessages.h" static void * kJSQMessagesInputToolbarKeyValueObservingContext = &kJSQMessagesInputToolbarKeyValueObservingContext; @interface JSQMessagesInputToolbar () @property (assign, nonatomic) BOOL jsq_isObserving; @end @implementation JSQMessagesInputToolbar @dynamic delegate; #pragma mark - Initialization - (void)awakeFromNib { [super awakeFromNib]; self.backgroundColor = [UIColor whiteColor]; self.jsq_isObserving = NO; self.sendButtonLocation = JSQMessagesInputSendButtonLocationRight; self.enablesSendButtonAutomatically = YES; self.preferredDefaultHeight = 44.0f; self.maximumHeight = NSNotFound; JSQMessagesToolbarContentView *toolbarContentView = [self loadToolbarContentView]; toolbarContentView.frame = self.frame; [self addSubview:toolbarContentView]; [self jsq_pinAllEdgesOfSubview:toolbarContentView]; [self setNeedsUpdateConstraints]; _contentView = toolbarContentView; [self jsq_addObservers]; JSQMessagesToolbarButtonFactory *toolbarButtonFactory = [[JSQMessagesToolbarButtonFactory alloc] initWithFont:[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]]; self.contentView.leftBarButtonItem = [toolbarButtonFactory defaultAccessoryButtonItem]; self.contentView.rightBarButtonItem = [toolbarButtonFactory defaultSendButtonItem]; [self updateSendButtonEnabledState]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewTextDidChangeNotification:) name:UITextViewTextDidChangeNotification object:_contentView.textView]; } - (JSQMessagesToolbarContentView *)loadToolbarContentView { NSArray *nibViews = [[NSBundle bundleForClass:[JSQMessagesInputToolbar class]] loadNibNamed:NSStringFromClass([JSQMessagesToolbarContentView class]) owner:nil options:nil]; return nibViews.firstObject; } - (void)dealloc { [self jsq_removeObservers]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - Setters - (void)setPreferredDefaultHeight:(CGFloat)preferredDefaultHeight { NSParameterAssert(preferredDefaultHeight > 0.0f); _preferredDefaultHeight = preferredDefaultHeight; } - (void)setEnablesSendButtonAutomatically:(BOOL)enablesSendButtonAutomatically { _enablesSendButtonAutomatically = enablesSendButtonAutomatically; [self updateSendButtonEnabledState]; } #pragma mark - Actions - (void)jsq_leftBarButtonPressed:(UIButton *)sender { [self.delegate messagesInputToolbar:self didPressLeftBarButton:sender]; } - (void)jsq_rightBarButtonPressed:(UIButton *)sender { [self.delegate messagesInputToolbar:self didPressRightBarButton:sender]; } #pragma mark - Input toolbar - (void)updateSendButtonEnabledState { if (!self.enablesSendButtonAutomatically) { return; } BOOL enabled = [self.contentView.textView hasText]; switch (self.sendButtonLocation) { case JSQMessagesInputSendButtonLocationRight: self.contentView.rightBarButtonItem.enabled = enabled; break; case JSQMessagesInputSendButtonLocationLeft: self.contentView.leftBarButtonItem.enabled = enabled; break; default: break; } } #pragma mark - Notifications - (void)textViewTextDidChangeNotification:(NSNotification *)notification { [self updateSendButtonEnabledState]; } #pragma mark - Key-value observing - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 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 updateSendButtonEnabledState]; } } } - (void)jsq_addObservers { 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; } - (void)jsq_removeObservers { if (!_jsq_isObserving) { return; } @try { [_contentView removeObserver:self forKeyPath:NSStringFromSelector(@selector(leftBarButtonItem)) context:kJSQMessagesInputToolbarKeyValueObservingContext]; [_contentView removeObserver:self forKeyPath:NSStringFromSelector(@selector(rightBarButtonItem)) context:kJSQMessagesInputToolbarKeyValueObservingContext]; } @catch (NSException *__unused exception) { } _jsq_isObserving = NO; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesLabel.h ================================================ // // 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 /** * `JSQMessagesLabel` is a subclass of `UILabel` that adds support for a `textInsets` property, * which is similar to the `textContainerInset` property of `UITextView`. */ @interface JSQMessagesLabel : UILabel /** * The inset of the text layout area within the label's content area. The default value is `UIEdgeInsetsZero`. * * @discussion This property provides text margins for the text laid out in the label. * The inset values provided must be greater than or equal to `0.0f`. */ @property (assign, nonatomic) UIEdgeInsets textInsets; @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesLabel.m ================================================ // // 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 "JSQMessagesLabel.h" @implementation JSQMessagesLabel #pragma mark - Initialization - (void)jsq_configureLabel { [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.textInsets = UIEdgeInsetsZero; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self jsq_configureLabel]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self jsq_configureLabel]; } #pragma mark - Setters - (void)setTextInsets:(UIEdgeInsets)textInsets { if (UIEdgeInsetsEqualToEdgeInsets(_textInsets, textInsets)) { return; } _textInsets = textInsets; [self setNeedsDisplay]; } #pragma mark - Drawing - (void)drawTextInRect:(CGRect)rect { [super drawTextInRect:CGRectMake(CGRectGetMinX(rect) + self.textInsets.left, CGRectGetMinY(rect) + self.textInsets.top, CGRectGetWidth(rect) - self.textInsets.right, CGRectGetHeight(rect) - self.textInsets.bottom)]; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.h ================================================ // // 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 @class JSQMessagesLoadEarlierHeaderView; /** * A constant defining the default height of a `JSQMessagesLoadEarlierHeaderView`. */ FOUNDATION_EXPORT const CGFloat kJSQMessagesLoadEarlierHeaderViewHeight; NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesLoadEarlierHeaderViewDelegate` defines methods that allow you to * respond to interactions within the header view. */ @protocol JSQMessagesLoadEarlierHeaderViewDelegate @required /** * Tells the delegate that the loadButton has received a touch event. * * @param headerView The header view that contains the sender. * @param sender The button that received the touch. */ - (void)headerView:(JSQMessagesLoadEarlierHeaderView *)headerView didPressLoadButton:(UIButton *)sender; @end /** * The `JSQMessagesLoadEarlierHeaderView` class implements a reusable view that can be placed * at the top of a `JSQMessagesCollectionView`. This view contains a "load earlier messages" button * and can be used as a way for the user to load previously sent messages. */ @interface JSQMessagesLoadEarlierHeaderView : UICollectionReusableView /** * The object that acts as the delegate of the header view. */ @property (weak, nonatomic, nullable) id delegate; /** * Returns the load button of the header view. */ @property (weak, nonatomic, readonly, nullable) UIButton *loadButton; #pragma mark - Class methods /** * Returns the `UINib` object initialized for the collection reusable view. * * @return The initialized `UINib` object. */ + (UINib *)nib; /** * Returns the default string used to identify the reusable header view. * * @return The string used to identify the reusable header view. */ + (NSString *)headerReuseIdentifier; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.m ================================================ // // 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 "JSQMessagesLoadEarlierHeaderView.h" #import "NSBundle+JSQMessages.h" const CGFloat kJSQMessagesLoadEarlierHeaderViewHeight = 32.0f; @interface JSQMessagesLoadEarlierHeaderView () @property (weak, nonatomic) IBOutlet UIButton *loadButton; @end @implementation JSQMessagesLoadEarlierHeaderView #pragma mark - Class methods + (UINib *)nib { return [UINib nibWithNibName:NSStringFromClass([JSQMessagesLoadEarlierHeaderView class]) bundle:[NSBundle bundleForClass:[JSQMessagesLoadEarlierHeaderView class]]]; } + (NSString *)headerReuseIdentifier { return NSStringFromClass([JSQMessagesLoadEarlierHeaderView class]); } #pragma mark - Initialization - (void)awakeFromNib { [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]; } - (void)dealloc { _loadButton = nil; _delegate = nil; } #pragma mark - Reusable view - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; self.loadButton.backgroundColor = backgroundColor; } #pragma mark - Actions - (IBAction)loadButtonPressed:(UIButton *)sender { [self.delegate headerView:self didPressLoadButton:sender]; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.xib ================================================ ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesMediaPlaceholderView.h ================================================ // // 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 #import NS_ASSUME_NONNULL_BEGIN /** * A `JSQMessagesMediaPlaceholderView` object represents a loading or placeholder * view for media message objects whose media attachments are not yet available. * When sending or receiving media messages that must be uploaded or downloaded from the network, * you may display this view temporarily until the media attachement is available. * You should return an instance of this class from the `mediaPlaceholderView` method in * the `JSQMessageMediaData` protocol. * * @see JSQMessageMediaData. */ @interface JSQMessagesMediaPlaceholderView : UIView /** * Returns the activity indicator view for this placeholder view, or `nil` if it does not exist. */ @property (nonatomic, weak, readonly, nullable) UIActivityIndicatorView *activityIndicatorView; /** * Returns the image view for this placeholder view, or `nil` if it does not exist. */ @property (nonatomic, weak, readonly, nullable) UIImageView *imageView; /** * Creates a media placeholder view object with a light gray background and * a centered activity indicator. * * @discussion When initializing a `JSQMessagesMediaPlaceholderView` with this method, * its imageView property will be nil. * * @return An initialized `JSQMessagesMediaPlaceholderView` object. */ + (instancetype)viewWithActivityIndicator; /** * Creates a media placeholder view object with a light gray background and * a centered paperclip attachment icon. * * @discussion When initializing a `JSQMessagesMediaPlaceholderView` with this method, * its activityIndicatorView property will be nil. * * @return An initialized `JSQMessagesMediaPlaceholderView` object. */ + (instancetype)viewWithAttachmentIcon; /** * Creates a media placeholder view having the given frame, backgroundColor, and activityIndicatorView. * * @param frame A rectangle defining the frame of the view. This value must be a non-zero, non-null rectangle. * @param backgroundColor The background color of the view. This value must not be `nil`. * @param activityIndicatorView An initialized activity indicator to be added and centered in the view. This value must not be `nil`. * * @return An initialized `JSQMessagesMediaPlaceholderView` object. */ - (instancetype)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor activityIndicatorView:(UIActivityIndicatorView *)activityIndicatorView; /** * Creates a media placeholder view having the given frame, backgroundColor, and imageView. * * @param frame A rectangle defining the frame of the view. This value must be a non-zero, non-null rectangle. * @param backgroundColor The background color of the view. This value must not be `nil`. * @param imageView An initialized image view to be added and centered in the view. This value must not be `nil`. * * @return An initialized `JSQMessagesMediaPlaceholderView` object. */ - (instancetype)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor imageView:(UIImageView *)imageView; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesMediaPlaceholderView.m ================================================ // // 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 "JSQMessagesMediaPlaceholderView.h" #import "UIColor+JSQMessages.h" #import "UIImage+JSQMessages.h" @implementation JSQMessagesMediaPlaceholderView #pragma mark - Init + (instancetype)viewWithActivityIndicator { UIColor *lightGrayColor = [UIColor jsq_messageBubbleLightGrayColor]; UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; spinner.color = [lightGrayColor jsq_colorByDarkeningColorWithValue:0.4f]; JSQMessagesMediaPlaceholderView *view = [[JSQMessagesMediaPlaceholderView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 200.0f, 120.0f) backgroundColor:lightGrayColor activityIndicatorView:spinner]; return view; } + (instancetype)viewWithAttachmentIcon { UIColor *lightGrayColor = [UIColor jsq_messageBubbleLightGrayColor]; UIImage *paperclip = [[UIImage jsq_defaultAccessoryImage] jsq_imageMaskedWithColor:[lightGrayColor jsq_colorByDarkeningColorWithValue:0.4f]]; UIImageView *imageView = [[UIImageView alloc] initWithImage:paperclip]; JSQMessagesMediaPlaceholderView *view =[[JSQMessagesMediaPlaceholderView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 200.0f, 120.0f) backgroundColor:lightGrayColor imageView:imageView]; return view; } - (instancetype)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor activityIndicatorView:(UIActivityIndicatorView *)activityIndicatorView { NSParameterAssert(activityIndicatorView != nil); self = [self initWithFrame:frame backgroundColor:backgroundColor]; if (self) { [self addSubview:activityIndicatorView]; _activityIndicatorView = activityIndicatorView; _activityIndicatorView.center = self.center; [_activityIndicatorView startAnimating]; _imageView = nil; } return self; } - (instancetype)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor imageView:(UIImageView *)imageView { NSParameterAssert(imageView != nil); self = [self initWithFrame:frame backgroundColor:backgroundColor]; if (self) { [self addSubview:imageView]; _imageView = imageView; _imageView.center = self.center; _activityIndicatorView = nil; } return self; } - (instancetype)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor { NSParameterAssert(!CGRectEqualToRect(frame, CGRectNull)); NSParameterAssert(!CGRectEqualToRect(frame, CGRectZero)); NSParameterAssert(backgroundColor != nil); self = [super initWithFrame:frame]; if (self) { self.backgroundColor = backgroundColor; self.userInteractionEnabled = NO; self.clipsToBounds = YES; self.contentMode = UIViewContentModeScaleAspectFill; } return self; } #pragma mark - Layout - (void)layoutSubviews { [super layoutSubviews]; if (self.activityIndicatorView) { self.activityIndicatorView.center = self.center; } else if (self.imageView) { self.imageView.center = self.center; } } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesToolbarContentView.h ================================================ // // 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 #import #import "JSQMessagesComposerTextView.h" NS_ASSUME_NONNULL_BEGIN /** * A constant value representing the default spacing to use for the left and right edges * of the toolbar content view. */ FOUNDATION_EXPORT const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault; /** * A `JSQMessagesToolbarContentView` represents the content displayed in a `JSQMessagesInputToolbar`. * These subviews consist of a left button, a text view, and a right button. One button is used as * the send button, and the other as the accessory button. The text view is used for composing messages. */ @interface JSQMessagesToolbarContentView : UIView /** * Returns the text view in which the user composes a message. */ @property (weak, nonatomic, readonly, nullable) JSQMessagesComposerTextView *textView; /** * A custom button item displayed on the left of the toolbar content view. * * @discussion The frame height of this button is ignored. When you set this property, the button * is fitted within a pre-defined default content view, the leftBarButtonContainerView, * whose height is determined by the height of the toolbar. However, the width of this button * will be preserved. You may specify a new width using `leftBarButtonItemWidth`. * If the frame of this button is equal to `CGRectZero` when set, then a default frame size will be used. * Set this value to `nil` to remove the button. */ @property (weak, nonatomic, nullable) UIButton *leftBarButtonItem; /** * Specifies the width of the leftBarButtonItem. * * @discussion This property modifies the width of the leftBarButtonContainerView. */ @property (assign, nonatomic) CGFloat leftBarButtonItemWidth; /** * Specifies the amount of spacing between the content view and the leading edge of leftBarButtonItem. * * @discussion The default value is `8.0f`. */ @property (assign, nonatomic) CGFloat leftContentPadding; /** * The container view for the leftBarButtonItem. * * @discussion * You may use this property to add additional button items to the left side of the toolbar content view. * However, you will be completely responsible for responding to all touch events for these buttons * in your `JSQMessagesViewController` subclass. */ @property (weak, nonatomic, readonly, nullable) UIView *leftBarButtonContainerView; /** * A custom button item displayed on the right of the toolbar content view. * * @discussion The frame height of this button is ignored. When you set this property, the button * is fitted within a pre-defined default content view, the rightBarButtonContainerView, * whose height is determined by the height of the toolbar. However, the width of this button * will be preserved. You may specify a new width using `rightBarButtonItemWidth`. * If the frame of this button is equal to `CGRectZero` when set, then a default frame size will be used. * Set this value to `nil` to remove the button. */ @property (weak, nonatomic, nullable) UIButton *rightBarButtonItem; /** * Specifies the width of the rightBarButtonItem. * * @discussion This property modifies the width of the rightBarButtonContainerView. */ @property (assign, nonatomic) CGFloat rightBarButtonItemWidth; /** * Specifies the amount of spacing between the content view and the trailing edge of rightBarButtonItem. * * @discussion The default value is `8.0f`. */ @property (assign, nonatomic) CGFloat rightContentPadding; /** * The container view for the rightBarButtonItem. * * @discussion * You may use this property to add additional button items to the right side of the toolbar content view. * However, you will be completely responsible for responding to all touch events for these buttons * in your `JSQMessagesViewController` subclass. */ @property (weak, nonatomic, readonly, nullable) UIView *rightBarButtonContainerView; #pragma mark - Class methods /** * Returns the `UINib` object initialized for a `JSQMessagesToolbarContentView`. * * @return The initialized `UINib` object. */ + (UINib *)nib; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesToolbarContentView.m ================================================ // // 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 "JSQMessagesToolbarContentView.h" #import "UIView+JSQMessages.h" const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingDefault = 8.0f; @interface JSQMessagesToolbarContentView () @property (weak, nonatomic) IBOutlet JSQMessagesComposerTextView *textView; @property (weak, nonatomic) IBOutlet UIView *leftBarButtonContainerView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftBarButtonContainerViewWidthConstraint; @property (weak, nonatomic) IBOutlet UIView *rightBarButtonContainerView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightBarButtonContainerViewWidthConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftHorizontalSpacingConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightHorizontalSpacingConstraint; @end @implementation JSQMessagesToolbarContentView #pragma mark - Class methods + (UINib *)nib { return [UINib nibWithNibName:NSStringFromClass([JSQMessagesToolbarContentView class]) bundle:[NSBundle bundleForClass:[JSQMessagesToolbarContentView class]]]; } #pragma mark - Initialization - (void)awakeFromNib { [super awakeFromNib]; [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.leftHorizontalSpacingConstraint.constant = kJSQMessagesToolbarContentViewHorizontalSpacingDefault; self.rightHorizontalSpacingConstraint.constant = kJSQMessagesToolbarContentViewHorizontalSpacingDefault; self.backgroundColor = [UIColor clearColor]; } #pragma mark - Setters - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; self.leftBarButtonContainerView.backgroundColor = backgroundColor; self.rightBarButtonContainerView.backgroundColor = backgroundColor; } - (void)setLeftBarButtonItem:(UIButton *)leftBarButtonItem { if (_leftBarButtonItem) { [_leftBarButtonItem removeFromSuperview]; } if (!leftBarButtonItem) { _leftBarButtonItem = nil; self.leftHorizontalSpacingConstraint.constant = 0.0f; self.leftBarButtonItemWidth = 0.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; } - (void)setLeftBarButtonItemWidth:(CGFloat)leftBarButtonItemWidth { self.leftBarButtonContainerViewWidthConstraint.constant = leftBarButtonItemWidth; [self setNeedsUpdateConstraints]; } - (void)setRightBarButtonItem:(UIButton *)rightBarButtonItem { if (_rightBarButtonItem) { [_rightBarButtonItem removeFromSuperview]; } if (!rightBarButtonItem) { _rightBarButtonItem = nil; self.rightHorizontalSpacingConstraint.constant = 0.0f; self.rightBarButtonItemWidth = 0.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; } - (void)setRightBarButtonItemWidth:(CGFloat)rightBarButtonItemWidth { self.rightBarButtonContainerViewWidthConstraint.constant = rightBarButtonItemWidth; [self setNeedsUpdateConstraints]; } - (void)setRightContentPadding:(CGFloat)rightContentPadding { self.rightHorizontalSpacingConstraint.constant = rightContentPadding; [self setNeedsUpdateConstraints]; } - (void)setLeftContentPadding:(CGFloat)leftContentPadding { self.leftHorizontalSpacingConstraint.constant = leftContentPadding; [self setNeedsUpdateConstraints]; } #pragma mark - Getters - (CGFloat)leftBarButtonItemWidth { return self.leftBarButtonContainerViewWidthConstraint.constant; } - (CGFloat)rightBarButtonItemWidth { return self.rightBarButtonContainerViewWidthConstraint.constant; } - (CGFloat)rightContentPadding { return self.rightHorizontalSpacingConstraint.constant; } - (CGFloat)leftContentPadding { return self.leftHorizontalSpacingConstraint.constant; } #pragma mark - UIView overrides - (void)setNeedsDisplay { [super setNeedsDisplay]; [self.textView setNeedsDisplay]; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesToolbarContentView.xib ================================================ ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.h ================================================ // // 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 #import /** * A constant defining the default height of a `JSQMessagesTypingIndicatorFooterView`. */ FOUNDATION_EXPORT const CGFloat kJSQMessagesTypingIndicatorFooterViewHeight; NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesTypingIndicatorFooterView` class implements a reusable view that can be placed * at the bottom of a `JSQMessagesCollectionView`. This view represents a typing indicator * for incoming messages. */ @interface JSQMessagesTypingIndicatorFooterView : UICollectionReusableView #pragma mark - Class methods /** * Returns the `UINib` object initialized for the collection reusable view. * * @return The initialized `UINib` object. */ + (UINib *)nib; /** * Returns the default string used to identify the reusable footer view. * * @return The string used to identify the reusable footer view. */ + (NSString *)footerReuseIdentifier; #pragma mark - Typing indicator /** * Configures the receiver with the specified attributes for the given collection view. * Call this method after dequeuing the footer view. * * @param ellipsisColor The color of the typing indicator ellipsis. This value must not be `nil`. * @param messageBubbleColor The color of the typing indicator message bubble. This value must not be `nil`. * @param animated Specifies whether the typing indicator should animate. * @param shouldDisplayOnLeft Specifies whether the typing indicator displays on the left or right side of the collection view when displayed. * @param collectionView The collection view in which the footer view will appear. This value must not be `nil`. */ - (void)configureWithEllipsisColor:(UIColor *)ellipsisColor messageBubbleColor:(UIColor *)messageBubbleColor animated:(BOOL)animated shouldDisplayOnLeft:(BOOL)shouldDisplayOnLeft forCollectionView:(UICollectionView *)collectionView; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.m ================================================ // // 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 "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesBubbleImageFactory.h" #import "JSQMessagesTypingView.h" #import "UIImage+JSQMessages.h" #import "UIColor+JSQMessages.h" const CGFloat kJSQMessagesTypingIndicatorFooterViewHeight = 46.0f; @interface JSQMessagesTypingIndicatorFooterView () @property (weak, nonatomic) IBOutlet UIImageView *bubbleImageView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bubbleImageViewRightHorizontalConstraint; @property (weak, nonatomic) IBOutlet JSQMessagesTypingView *typingView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *typingIndicatorImageViewRightHorizontalConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *typingIndicatorToBubbleImageAlignConstraint; @end @implementation JSQMessagesTypingIndicatorFooterView #pragma mark - Class methods + (UINib *)nib { return [UINib nibWithNibName:NSStringFromClass([JSQMessagesTypingIndicatorFooterView class]) bundle:[NSBundle bundleForClass:[JSQMessagesTypingIndicatorFooterView class]]]; } + (NSString *)footerReuseIdentifier { return NSStringFromClass([JSQMessagesTypingIndicatorFooterView class]); } #pragma mark - Initialization - (void)awakeFromNib { [super awakeFromNib]; [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.backgroundColor = [UIColor clearColor]; self.userInteractionEnabled = NO; } #pragma mark - Reusable view - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; self.bubbleImageView.backgroundColor = backgroundColor; } #pragma mark - Typing indicator - (void)configureWithEllipsisColor:(UIColor *)ellipsisColor messageBubbleColor:(UIColor *)messageBubbleColor animated:(BOOL)animated shouldDisplayOnLeft:(BOOL)shouldDisplayOnLeft forCollectionView:(UICollectionView *)collectionView { NSParameterAssert(ellipsisColor != nil); NSParameterAssert(messageBubbleColor != nil); NSParameterAssert(collectionView != nil); CGFloat bubbleMarginMinimumSpacing = 6.0f; JSQMessagesBubbleImageFactory *bubbleImageFactory = [[JSQMessagesBubbleImageFactory alloc] init]; if (shouldDisplayOnLeft) { self.bubbleImageView.image = [bubbleImageFactory incomingMessagesBubbleImageWithColor:messageBubbleColor].messageBubbleImage; CGFloat collectionViewWidth = CGRectGetWidth(collectionView.frame); CGFloat bubbleWidth = CGRectGetWidth(self.bubbleImageView.frame); CGFloat bubbleMarginMaximumSpacing = collectionViewWidth - bubbleWidth - bubbleMarginMinimumSpacing; self.bubbleImageViewRightHorizontalConstraint.constant = bubbleMarginMaximumSpacing; self.typingIndicatorToBubbleImageAlignConstraint.constant = 0; } else { self.bubbleImageView.image = [bubbleImageFactory outgoingMessagesBubbleImageWithColor:messageBubbleColor].messageBubbleImage; self.bubbleImageViewRightHorizontalConstraint.constant = bubbleMarginMinimumSpacing; self.typingIndicatorToBubbleImageAlignConstraint.constant = 6; } [self setNeedsUpdateConstraints]; self.typingView.dotsColor = ellipsisColor; self.typingView.animateToColor = [ellipsisColor jsq_colorByDarkeningColorWithValue:0.2f]; self.typingView.animated = animated; self.typingView.animationDuration = 1.33; if (animated) { CAAnimation *pulseAnimation = [self pulseAnimation]; pulseAnimation.duration = self.typingView.animationDuration * 2; [self.bubbleImageView.layer addAnimation:pulseAnimation forKey:@"pulsing"]; } } - (CAKeyframeAnimation *)pulseAnimation { CAKeyframeAnimation *pulseAnimation = [CAKeyframeAnimation animation]; pulseAnimation.keyPath = @"transform"; pulseAnimation.values = @[ [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)], [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.03, 0.97, 1.0)], [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)], [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.97, 1.03, 1.0)], [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)] ]; pulseAnimation.keyTimes = @[ @0, @(1/4.0), @(1/2.0), @(3/4.0), @1 ]; pulseAnimation.repeatCount = NSIntegerMax; pulseAnimation.autoreverses = YES; return pulseAnimation; } @end ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.xib ================================================ ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesTypingView.h ================================================ // // 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 // TODO: write documentation // // https://github.com/jessesquires/JSQMessagesViewController/issues/1647 // NS_ASSUME_NONNULL_BEGIN @interface JSQMessagesTypingView : UIView @property (strong, nonatomic) UIColor *dotsColor; @property (strong, nonatomic) UIColor *animateToColor; @property (assign, nonatomic) CGFloat animationDuration; @property (assign, nonatomic) BOOL animated; @end NS_ASSUME_NONNULL_END ================================================ FILE: JSQMessagesViewController/Views/JSQMessagesTypingView.m ================================================ // // 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 "JSQMessagesTypingView.h" @interface JSQMessagesTypingView() @property (strong, nonatomic) CAShapeLayer *dot; @end @implementation JSQMessagesTypingView #pragma mark - Init - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setup]; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { [self setup]; } return self; } - (void)setup { _animated = NO; _animationDuration = 1.33; _dotsColor = [UIColor lightGrayColor]; _animateToColor = [UIColor grayColor]; // Center points of dots on x axis are [2/7, 1/2, 5/7] of total view width. // Size od dot is aprox ~7.125 times smaller than view width. CGFloat dotDimension = self.frame.size.width / 7.125; CGFloat firstDotCenterX = 2 * self.frame.size.width / 7; CGFloat intervalBetweenDotsOnXAxis = 3.0 * self.frame.size.width / 14.0; CAReplicatorLayer *container = [[CAReplicatorLayer alloc] init]; container.position = CGPointMake(self.layer.bounds.size.width / 2.0, self.layer.bounds.size.height / 2.0); container.bounds = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); container.instanceCount = 3; container.instanceTransform = CATransform3DMakeTranslation(intervalBetweenDotsOnXAxis, 0.0, 0.0); container.instanceDelay = self.animationDuration / 7.0; CAShapeLayer *dot = [[CAShapeLayer alloc] init]; dot.position = CGPointMake(firstDotCenterX, container.bounds.size.height / 2.0); dot.bounds = CGRectMake(0, 0, dotDimension, dotDimension); dot.path = [UIBezierPath bezierPathWithOvalInRect:dot.bounds].CGPath; dot.fillColor = self.dotsColor.CGColor; self.dot = dot; [container addSublayer:dot]; [self.layer addSublayer:container]; [self updateAnimation]; } #pragma mark - Setters - (void)setAnimated:(BOOL)animated { _animated = animated; [self updateAnimation]; } - (void)setDotsColor:(UIColor *)dotsColor { _dotsColor = dotsColor; self.dot.fillColor = dotsColor.CGColor; [self updateAnimation]; } - (void)setAnimateToColor:(UIColor *)animateToColor { _animateToColor = animateToColor; [self updateAnimation]; } #pragma mark - Helpers - (void)updateAnimation { [self.dot removeAnimationForKey:@"darkening"]; if (_animated) { [self.dot addAnimation:[self fillColorAnimation] forKey:@"darkening"]; } } - (CAKeyframeAnimation *)fillColorAnimation { CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"fillColor"; animation.values = @[ (id)self.dotsColor.CGColor, (id)self.dotsColor.CGColor, (id)self.animateToColor.CGColor, (id)self.dotsColor.CGColor, (id)self.dotsColor.CGColor, ]; animation.keyTimes = @[ @0, @(2/7.0), @(1/2.0), @(5/7.0), @1 ]; animation.duration = self.animationDuration; animation.repeatCount = NSIntegerMax; animation.autoreverses = YES; return animation; } @end ================================================ FILE: JSQMessagesViewController.podspec ================================================ Pod::Spec.new do |s| s.name = 'JSQMessagesViewController' s.version = '7.3.5' s.summary = 'An elegant messages UI library for iOS.' s.license = 'MIT' s.platform = :ios, '7.0' s.author = 'Jesse Squires' 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', 'MobileCoreServices', 'AVFoundation' s.requires_arc = true s.deprecated = true end ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2013-present Jesse Squires http://www.jessesquires.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) # :warning: Deprecated :warning: This library is deprecated. Please read [my blog post](http://www.jessesquires.com/blog/officially-deprecating-jsqmessagesviewcontroller/) for details. ![JSQMessagesViewController banner](https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Assets/jsq_messages_banner.png) [![Build Status](https://secure.travis-ci.org/jessesquires/JSQMessagesViewController.svg)](https://travis-ci.org/jessesquires/JSQMessagesViewController) [![Version Status](https://img.shields.io/cocoapods/v/JSQMessagesViewController.svg)][podLink] [![license MIT](https://img.shields.io/cocoapods/l/JSQMessagesViewController.svg)][mitLink] [![codecov](https://codecov.io/gh/jessesquires/JSQMessagesViewController/branch/develop/graph/badge.svg)](https://codecov.io/gh/jessesquires/JSQMessagesViewController) [![Platform](https://img.shields.io/cocoapods/p/JSQMessagesViewController.svg)][docsLink] ------------------------ ![Screenshot0][img0]    ![Screenshot1][img1]    ![Screenshot2][img2]    ![Screenshot3][img3] > More screenshots available at [CocoaControls](https://www.cocoacontrols.com/controls/jsqmessagesviewcontroller) ## Features See the [website](http://jessesquires.github.io/JSQMessagesViewController) for the list of features. ## Design Goals - Closely mimic [iMessage](http://www.apple.com/ios/messages/) style and behavior - [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) design - Easy customization and extension for clients ## Dependencies * [JSQSystemSoundPlayer][playerLink] ## Requirements * iOS 7.0+ * ARC ## Installation ### [CocoaPods](https://cocoapods.org/) (recommended) ````ruby # For latest release in cocoapods pod 'JSQMessagesViewController' # Latest on develop pod 'JSQMessagesViewController', :git => 'https://github.com/jessesquires/JSQMessagesViewController.git', :branch => 'develop' ```` ## Getting Started See the [Getting Started](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/getting_started.md) guide! ## Questions & Help * Review the [FAQ](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/faq.md). * Search issues for previous and current [questions](https://github.com/jessesquires/JSQMessagesViewController/issues?utf8=✓&q=label%3A%22questions+%26+help%22+). *Do not open duplicates.* * [StackOverflow](http://stackoverflow.com/questions/tagged/jsqmessagesviewcontroller) is often the most appropriate place for questions and help. We have our own tag, `jsqmessagesviewcontroller`. * See the [Migration Guide](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/migration.md) for migrating between major versions of the library. * **Only ask questions that are _specific_ to this library.** * **Please avoid emailing questions.** I prefer to keep questions and their answers open-source. ## Documentation Read the docs, [available here][docsLink] via [@CocoaDocs](https://twitter.com/CocoaDocs). ## Core team - Jesse Squires ([**@jesse_squires**](https://twitter.com/jesse_squires)) - Harlan Haskans ([**@harlanhaskins**](https://github.com/harlanhaskins)) - Eli Burke ([**@eliburke**](https://github.com/eliburke)) - Sebastian Ludwig ([**@sebastianludwig**](https://github.com/sebastianludwig)) - Lucas Huang ([**@Lucashuang0802**](https://github.com/Lucashuang0802)) - Dan Leonard ([**@macmedan**](https://github.com/macmedan)) ## Contributing Please follow these sweet [contribution guidelines](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/CONTRIBUTING.md). > **Interested in becoming a core contributor with push access? See our [onboarding guide](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/contributor_onboarding.md) for details.** ## Credits * Created and maintained by [**@jesse_squires**](https://twitter.com/jesse_squires). * Many thanks to [**the contributors**](https://github.com/jessesquires/JSQMessagesViewController/graphs/contributors) of this project. * iOS assets extracted using [**@0xced**](https://github.com/0xced) / [iOS-Artwork-Extractor](https://github.com/0xced/iOS-Artwork-Extractor). ## Apps using this library According to [CocoaPods stats](https://cocoapods.org/pods/JSQMessagesViewController), over **36,000 apps** are using `JSQMessagesViewController`. [Here are the ones](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/apps_using_this_library.md) that we know about. Please submit a [pull request](https://github.com/jessesquires/JSQMessagesViewController/compare) to add your app! :smile: ## License `JSQMessagesViewController` is released under an [MIT License][mitLink]. See `LICENSE` for details. >**Copyright © 2013-present Jesse Squires.** *Please provide attribution, it is greatly appreciated.* [docsLink]:http://cocoadocs.org/docsets/JSQMessagesViewController/ [podLink]:https://cocoapods.org/pods/JSQMessagesViewController [mitLink]:http://opensource.org/licenses/MIT [playerLink]:https://github.com/jessesquires/JSQSystemSoundPlayer [img0]:https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot0.png [img1]:https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot1.png [img2]:https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot2.png [img3]:https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot3.png ================================================ FILE: SwiftExample/Podfile ================================================ # Uncomment this line to define a global platform for your project platform :ios, '9.0' # Uncomment this line if you're using Swift use_frameworks! target 'SwiftExample' do pod 'JSQMessagesViewController', :path => '../' end target 'SwiftExampleTests' do pod 'JSQMessagesViewController', :path => '../' end ================================================ FILE: SwiftExample/Pods/Local Podspecs/JSQMessagesViewController.podspec.json ================================================ { "name": "JSQMessagesViewController", "version": "7.3.4", "summary": "An elegant messages UI library for iOS.", "homepage": "http://jessesquires.github.io/JSQMessagesViewController", "license": "MIT", "platforms": { "ios": "7.0" }, "authors": "Jesse Squires", "social_media_url": "https://twitter.com/jesse_squires", "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" ], "source": { "git": "https://github.com/jessesquires/JSQMessagesViewController.git", "tag": "7.3.4" }, "source_files": "JSQMessagesViewController/**/*.{h,m}", "resources": [ "JSQMessagesViewController/Assets/JSQMessagesAssets.bundle", "JSQMessagesViewController/**/*.{xib}" ], "frameworks": [ "QuartzCore", "CoreGraphics", "CoreLocation", "MapKit", "MobileCoreServices", "AVFoundation" ], "requires_arc": true } ================================================ FILE: SwiftExample/Pods/Pods.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 02B533E0835FB35880D73A0E77F7FF06 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7D42FB4AEE0C3244717BFD98C4F402 /* Foundation.framework */; }; 0971D308ACAB2C1715C743E52DB67352 /* JSQMediaItem.h in Headers */ = {isa = PBXBuildFile; fileRef = D350A894D316D0D75E96C1BEF67165D9 /* JSQMediaItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 09786FAEA00C46AE161738AA6769187E /* JSQMessagesToolbarContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D7360DA9FBCFFA0FDB1999596B4931CE /* JSQMessagesToolbarContentView.xib */; }; 0B73D8729FFE1530E2004AF2F2F1FAF5 /* JSQMessagesAvatarImageFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 96A13D464D35831CD34646417886B824 /* JSQMessagesAvatarImageFactory.m */; }; 0CCC464AF1B3662AF7FAC7C2DBB646A0 /* UIColor+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 952379E223739B709AB0FA1E3ADEBFB8 /* UIColor+JSQMessages.m */; }; 1286F46765A948304134C1BE15E17849 /* JSQMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 524E6097931B7ED772C33D9543B0CF15 /* JSQMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13702F016714682BE46AE7CF2FD69FA2 /* Pods-SwiftExample-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 09FC3F9B3A90D7404CBFA024E233743D /* Pods-SwiftExample-dummy.m */; }; 149907C7995310AE4ED9C04ECFFE50E8 /* JSQMessageBubbleImageDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 057981684A21DCBF08FCF9696AF5A257 /* JSQMessageBubbleImageDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16A087B7FE52F9BAE08AA28C059C2218 /* JSQMessageAvatarImageDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 15C66A457A812FA48828EC711F860B7D /* JSQMessageAvatarImageDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19515B0DDD209485D2030C4CDD58861A /* JSQMessagesMediaPlaceholderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3183A5D6D9FC13F7B60C5CD53898A57F /* JSQMessagesMediaPlaceholderView.m */; }; 1E794C200B2DC30F9771FA2A99746C54 /* Pods-SwiftExample-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = EDA523A8EB08F4D0F668C6C519714FB8 /* Pods-SwiftExample-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1F73A87E86E4F06F65C24C8D706C3D06 /* JSQMessagesCollectionViewCellOutgoing.xib in Resources */ = {isa = PBXBuildFile; fileRef = AC7DEECF66B9FDEB1DAD70299286263D /* JSQMessagesCollectionViewCellOutgoing.xib */; }; 2208A31A32190C3B7A2BEC5249DE9573 /* JSQMessagesBubbleImageFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = FBAF3B9685F118DCE6F73267E8184C0B /* JSQMessagesBubbleImageFactory.m */; }; 2497CF2AA7934A786AAF2D10FCD895CC /* JSQMessagesTimestampFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D939DED94CC13583A52A8202486FEA4 /* JSQMessagesTimestampFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2559B594BDFE7243E130FDDE5A998F3F /* JSQMessagesBubblesSizeCalculator.h in Headers */ = {isa = PBXBuildFile; fileRef = 70F9F509B25C0A3871DC1B80481296BE /* JSQMessagesBubblesSizeCalculator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 299BF0BD9436572EF05A8DD686426B38 /* JSQMessagesCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = AC7AAD02CE8BF396DF3DE2690B7BADB9 /* JSQMessagesCollectionView.m */; }; 2C1E614E1443B6DDC58ADC0D086DE7D1 /* JSQMessagesToolbarButtonFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = DE4241BAE287FC44CA02BE8D83C65D0F /* JSQMessagesToolbarButtonFactory.h */; settings = {ATTRIBUTES = (Public, ); }; }; 31CA73CACCCA6864F1D2DEEC98B6BE64 /* JSQMessagesAssets.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5F1CB8272344A931595EEDD067164659 /* JSQMessagesAssets.bundle */; }; 32CB2365204BAFC0BCD8FA1830845F17 /* JSQMessagesInputToolbar.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F57E9E440C5CD869A78C8C22056B25 /* JSQMessagesInputToolbar.m */; }; 3594E2AFC955F5ADD6AA0CE636733F90 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7D42FB4AEE0C3244717BFD98C4F402 /* Foundation.framework */; }; 36AE02087D184A7E56566950492D16F1 /* JSQMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = E007153261CE1297038828333F02E491 /* JSQMessages.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3C7D2FDBBEB3EE1A79EABA1AE899E6E4 /* JSQMessagesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8EF5F4DAE2096A1664B32E754587FC81 /* JSQMessagesViewController.xib */; }; 4221D9E061C1B880ABF28D86EF71A651 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11E7A34075E540D6F0C1816369F6A553 /* CoreLocation.framework */; }; 42D2BD077069B8043B6B1DF29FE0B769 /* NSBundle+JSQMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = F4D093DDB60343309422EFEEE9208A97 /* NSBundle+JSQMessages.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4327D100E30A840BE1270082EC81DAEB /* JSQMessagesTypingIndicatorFooterView.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EFEB1A2B305346C484DCC7F5945E742 /* JSQMessagesTypingIndicatorFooterView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 45063CC65F42020D48F704FF2A319A27 /* JSQMessagesMediaViewBubbleImageMasker.m in Sources */ = {isa = PBXBuildFile; fileRef = 16D8F43C9B0B02AB9CBC15639B532ED6 /* JSQMessagesMediaViewBubbleImageMasker.m */; }; 467F3A77A5336226B0DB8A7EA4826D11 /* JSQMessagesLoadEarlierHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F7B63B0F90D2D277319508F3F66267E /* JSQMessagesLoadEarlierHeaderView.m */; }; 48033F047B7C8DAD4C57E33B9FFFCAC7 /* JSQMessagesCollectionViewCellIncoming.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5F45B3B5C785B02B1CCEB51ED7087874 /* JSQMessagesCollectionViewCellIncoming.xib */; }; 4AD164814308A665D1F39A097358462B /* JSQVideoMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = C426EB071A2AAFB1B3F35B3D93255033 /* JSQVideoMediaItem.m */; }; 4B2B428728D8129A8C18685DDD777516 /* JSQMessagesCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = C1C01EE99D250147B553333270006D40 /* JSQMessagesCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4C34FFA14C2A67491C11A75438A97B80 /* JSQMessagesCollectionViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = F6AAD2332EC33082F93D3D22255524A2 /* JSQMessagesCollectionViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4CAFA12EA4BDAA9EF1C8346BA5AB346A /* JSQMessagesCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 625934269CDEAD7D986818EBE8571EEC /* JSQMessagesCollectionViewCell.m */; }; 4D95177D6F2A51F045425E260D7D083D /* JSQMessagesViewController-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C0343B4EB8D3C3448E9916FC309DB2C9 /* JSQMessagesViewController-dummy.m */; }; 5146B25DDEE785B138DEBA9916625FF1 /* JSQAudioMediaViewAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = F968E895B19778B91D72E71FFF217B27 /* JSQAudioMediaViewAttributes.m */; }; 51D1C525F7C4969E543A01CD17D44565 /* JSQPhotoMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 31B8479763A7110163F0F84342B0539C /* JSQPhotoMediaItem.m */; }; 57771709543EFBA38AEEC28E2916CE50 /* JSQMessagesLoadEarlierHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0F5B8A4363DBAD37018BEEF6F27B82FF /* JSQMessagesLoadEarlierHeaderView.xib */; }; 5B20E77C78ECFFBEE94E631C6D48B3A9 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F25BDE5C7405E48A57A7831625D551CD /* AVFoundation.framework */; }; 5C043F37E4D4417ECA44BCCB8C53E619 /* JSQMessageMediaData.h in Headers */ = {isa = PBXBuildFile; fileRef = 7811EEFD769234A5BE01FC929C1E16E2 /* JSQMessageMediaData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5CA041E616D1BF0896F033ABB98337B7 /* JSQMessagesLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = B3D0F109A53EDB30FF0D5F4658B63579 /* JSQMessagesLabel.m */; }; 618A6F67E2F5B3D799F2BE94168878F9 /* NSString+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = BCB1B6347793109A05327191F2CF1879 /* NSString+JSQMessages.m */; }; 61FC3013A1F5826C8FDE76ECDFDF755C /* NSBundle+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = D7AA00D128FAB1CFF7126516DCF234A6 /* NSBundle+JSQMessages.m */; }; 6478969CD4A1066DFB5300FF48566E13 /* JSQMessagesInputToolbar.h in Headers */ = {isa = PBXBuildFile; fileRef = 270A64B26DAEE6176110C90558CA5A04 /* JSQMessagesInputToolbar.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6D57B1EF4E9995010BBC9151D6D431E7 /* JSQPhotoMediaItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 76633CAF877874C0C5EDCAE536B689A2 /* JSQPhotoMediaItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6DB64D96EE290E26615F277C26FB8BDD /* JSQMessagesTypingView.m in Sources */ = {isa = PBXBuildFile; fileRef = D9FE093244C242BBC50C60B6C2408904 /* JSQMessagesTypingView.m */; }; 6EE0C10AB1CD73E4712704C5EBF36E06 /* JSQAudioMediaItem.h in Headers */ = {isa = PBXBuildFile; fileRef = C45B9EB1A4049C5465F2AAD260B904EE /* JSQAudioMediaItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 70E497827BBDDF1A6C3F1FE7AE096DA6 /* JSQMessagesCollectionViewCellOutgoing.m in Sources */ = {isa = PBXBuildFile; fileRef = F13184B4EDAA1DA57A10BA8BE02A0C4D /* JSQMessagesCollectionViewCellOutgoing.m */; }; 7142F1E3299EA9EFA1C84A1E3312393E /* NSString+JSQMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = DD2E9D8E624CB491CFA05C605A6BAE58 /* NSString+JSQMessages.h */; settings = {ATTRIBUTES = (Public, ); }; }; 71DC559E2BA0D17AE8E64CD561F408DB /* JSQMessagesCollectionViewFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = B9D05ABBE014A589E50C4DD3079147C7 /* JSQMessagesCollectionViewFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; 76A7469A8CC7AA4E71935F49DA02D91B /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C5E27903DA2690AF3381606582325D1 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7772024265D92D6412694019423F801C /* JSQMessagesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC221A7F5EA869AABD19B4DBA4794A3 /* JSQMessagesViewController.m */; }; 7ADBA442C9BCC7F8EC3A0FBBA607E756 /* JSQAudioMediaViewAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 181EC36DD60BF893E063F12833392833 /* JSQAudioMediaViewAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7D4C9DD82DE39240C76624031C3DCADE /* JSQMessagesCollectionViewCellOutgoing.h in Headers */ = {isa = PBXBuildFile; fileRef = 45D48AC1E79D5721C30FA3231CB3B13B /* JSQMessagesCollectionViewCellOutgoing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7F833206585AFA3C76D2CA6468F08C4C /* JSQMessagesComposerTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D24203D45F99479F6F517E995BB95D8 /* JSQMessagesComposerTextView.m */; }; 83FCD6BA1C85F35E81EAC5A114A1711C /* Pods-SwiftExampleTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 78B5DD2C75F208278D9E7C69E207EB84 /* Pods-SwiftExampleTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86285D5790B0FD08488082C749437BFC /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 000C5E7A807ADBFE2D79B69B99AFDD3F /* MapKit.framework */; }; 89988ADA3236378336D637CDC1705308 /* JSQMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 21DA795BA41ECCF30AFC0974C6EEFC85 /* JSQMessagesBubblesSizeCalculator.m */; }; 8C4D07E6D5A864CB5165954C17DC199C /* JSQMessagesCellTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = B8C4A3A79FF0482FD0C11FAE084DC372 /* JSQMessagesCellTextView.m */; }; 8DABA45AE25BA10088F6BA79B9895B37 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EA4A228E463A5D1E3FB740DA14001D0 /* MobileCoreServices.framework */; }; 8FD252B8B21CF2428E2A4E82AE1634A0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7D42FB4AEE0C3244717BFD98C4F402 /* Foundation.framework */; }; 94277C8660644DF12841EB2529FB8EF3 /* JSQMessagesComposerTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = E17F4220992E5BD3FC797A1D7D2DFF72 /* JSQMessagesComposerTextView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 944099AD67D13A4371F74124C9AADE3A /* JSQMessagesMediaPlaceholderView.h in Headers */ = {isa = PBXBuildFile; fileRef = 954B5BBA1444969ABE42B4BBC95DBE4A /* JSQMessagesMediaPlaceholderView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 95CF1A48AFF625E3052FAEF7BBFC049D /* JSQMessagesLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = 18A1292EAFE8E0D3CF56C41FEDDF665E /* JSQMessagesLabel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 973EC98CBB971ABD8610C8117AD93803 /* UIView+JSQMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 403BEDD017FB77FDC4E83A5601C64A0B /* UIView+JSQMessages.h */; settings = {ATTRIBUTES = (Public, ); }; }; 97FEE57845D475946B27A4CE1A6104FC /* JSQMessagesCollectionViewDelegateFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CA4F3FC4154CEFD439757AD85FB8FF0 /* JSQMessagesCollectionViewDelegateFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; 99CFD1B36B9F7FC889B5507813B46D4F /* JSQMessagesToolbarContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = B11D3AC96762A7BB8EE32D0708706561 /* JSQMessagesToolbarContentView.h */; settings = {ATTRIBUTES = (Public, ); }; }; A032B0420BE66EC5CFBAA119A095D42D /* JSQMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = FDDCDD7D41BF7D9167B90EE90F87DA01 /* JSQMessage.m */; }; A0F7CFFAB11AFFE78899BB963AF651DB /* UIColor+JSQMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B17F85F78315AB1381877780833F28C /* UIColor+JSQMessages.h */; settings = {ATTRIBUTES = (Public, ); }; }; A5ACA57BF20BD49AE7815646FBF2AEFC /* JSQMessagesTypingIndicatorFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76244FCF63EFB83275D112B57D45F690 /* JSQMessagesTypingIndicatorFooterView.xib */; }; A728F75F1D2CAC09027F6D49D88467C0 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ADAB1228EC10B7CB3572CF396510796A /* QuartzCore.framework */; }; A8107F45AA5D54B548D64490E09DCE76 /* JSQMessagesToolbarContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A03BEBEA2CF4310CF1B4DD56BF8F61E /* JSQMessagesToolbarContentView.m */; }; A89A6F15AC2DBAB06C84F9F83CE0A705 /* JSQAudioMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DADDDF43426A194DE729EB043B05B40 /* JSQAudioMediaItem.m */; }; A9DC2E25543160D5AE3048910F6889AB /* JSQMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CE60A4BE154D1FC51F8DA103E7B021B /* JSQMediaItem.m */; }; AB13B0284E39B5EA7FFA164DF8A3D9F0 /* JSQMessagesBubbleImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C8C570836044007BF20B0ED2FBCFE2A /* JSQMessagesBubbleImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; AFA9BE3313A21908984FB78302B83DAE /* JSQMessagesCollectionViewDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = FB651C0ABF8FEE00B5A7178CE4200D2B /* JSQMessagesCollectionViewDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; B478066D191A1EC33BC88923ECDFACBD /* JSQMessagesBubbleSizeCalculating.h in Headers */ = {isa = PBXBuildFile; fileRef = B8B84FA61D0E6F92976F225FC571ED64 /* JSQMessagesBubbleSizeCalculating.h */; settings = {ATTRIBUTES = (Public, ); }; }; B4B9EE079397BE229ED18E5E0B65DAE4 /* JSQMessagesViewAccessoryButtonDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A5053CFA2A987B6E737E6ACC79A3E3F /* JSQMessagesViewAccessoryButtonDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; B6F229A7CC57770B714476B5A0E780FC /* JSQMessagesBubbleImageFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 836783FE72029591ED844FA76BB6E28E /* JSQMessagesBubbleImageFactory.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8D33D0D75B4A3AE09DC7E36A1158D35 /* JSQMessagesCollectionViewCellIncoming.m in Sources */ = {isa = PBXBuildFile; fileRef = C83404BD601FB59DCB478B4806474BCF /* JSQMessagesCollectionViewCellIncoming.m */; }; B8FB0FC4261F6B329F2EFE2534A3ADBA /* JSQVideoMediaItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 97F9F2DE8856199D3C36743C6049350E /* JSQVideoMediaItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; BD6D7350FDCE9ECD71CE64FD4D0A296F /* JSQMessagesTimestampFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FED1F4D658C702CD3C0CD427CDFF0BE /* JSQMessagesTimestampFormatter.m */; }; BFA9CF241AB70C4CF7A00A1973B0F754 /* JSQMessagesAvatarImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C49A9F16A1D7A83619623B722B24E00 /* JSQMessagesAvatarImage.m */; }; C36557885745FB505AD57EF65EC4538F /* JSQMessagesTypingIndicatorFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = CCBDAC7B94A40319B10444540CB5761B /* JSQMessagesTypingIndicatorFooterView.m */; }; C780E3A896590680ACE7C79078190E27 /* JSQMessagesToolbarButtonFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = A153263CAA2EAE331530D8D20D432E0E /* JSQMessagesToolbarButtonFactory.m */; }; C7C143A2A75203893FCB3F984D5021A8 /* UIImage+JSQMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B50D70959F21FC83E3BF23C39EB8823 /* UIImage+JSQMessages.h */; settings = {ATTRIBUTES = (Public, ); }; }; C91D96396EF540192BDD3D2334B95AEA /* UIView+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = A107B4BA3E03390A14CDBB9E29604105 /* UIView+JSQMessages.m */; }; CA92CE252B96DD84DB04AAA5D6618577 /* JSQMessagesAvatarImageFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DBFA6E2DFD17C5DA8BD9A693E1ECC21 /* JSQMessagesAvatarImageFactory.h */; settings = {ATTRIBUTES = (Public, ); }; }; CB19BD71402A2C12929D93C0C037DB3A /* JSQLocationMediaItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 57FEE5C15DC269FDEF6AAE707DEFBCF1 /* JSQLocationMediaItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; CE52747511141C4E5ED4DFFA53CFEA09 /* UIImage+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = DDE72C64C54668D5527A9FDEF1A1008B /* UIImage+JSQMessages.m */; }; CF5C4883DDFE68428FA6E73A6BFD05FD /* JSQMessagesViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 90AEE1B44E6580033A25A89E0ED45E53 /* JSQMessagesViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D4E2E4076834A48E70FE2F62EB453B39 /* JSQLocationMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 68F2C50C784ACCCAFC21D9C1E6A8F2D3 /* JSQLocationMediaItem.m */; }; DC79DE76128A819B15C8E6BB9908CE18 /* JSQMessagesLoadEarlierHeaderView.h in Headers */ = {isa = PBXBuildFile; fileRef = D676EADFD0431F936F770C6D7EBD6747 /* JSQMessagesLoadEarlierHeaderView.h */; settings = {ATTRIBUTES = (Public, ); }; }; E089B530BE8AC615FC2BC0355B8B0D42 /* JSQMessagesCollectionViewCellIncoming.h in Headers */ = {isa = PBXBuildFile; fileRef = 297A5235136DACC67C3D66D02EA8ACB4 /* JSQMessagesCollectionViewCellIncoming.h */; settings = {ATTRIBUTES = (Public, ); }; }; E15B1C6287EE20BD8EA7079D0A877FC5 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 36A9B85D5390598A5B8CC766DF5F4E8E /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m */; }; E720AFD9BD8AB70A310818414B616037 /* JSQMessagesCellTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = E69951F70517FF9E9ED57B837A31BDB1 /* JSQMessagesCellTextView.h */; settings = {ATTRIBUTES = (Public, ); }; }; E9D44B069CEE8F69CEF692B8B6F689C3 /* Pods-SwiftExampleTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 91929344924005C6DCDD10292B0806B0 /* Pods-SwiftExampleTests-dummy.m */; }; EE33A0AEE5DE33EEEF5023E9F96E028C /* JSQMessagesBubbleImage.m in Sources */ = {isa = PBXBuildFile; fileRef = DFE2D283BE74583997BA411C0BD33988 /* JSQMessagesBubbleImage.m */; }; EF73B5848C16E35AA68AE5125B8F10BB /* JSQMessagesViewController-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 8518A94198081D09F20092C6ED6D0ECE /* JSQMessagesViewController-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; EFDFB9712A563AFEC8C458516F5353A6 /* JSQMessagesCollectionViewLayoutAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DA3D277828916C0263FA41AD20CDBE0 /* JSQMessagesCollectionViewLayoutAttributes.m */; }; F10BB39915DF7FD797B9406F64D33942 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB2010E848C23731711B6C75E7EC534F /* CoreGraphics.framework */; }; F3202A58AC1881E5A2CDD748617E1625 /* JSQMessagesCollectionViewLayoutAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = E6E6F5B09B62665CE9FDE461A6E43BC2 /* JSQMessagesCollectionViewLayoutAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; }; F47D16CE94FA55B9ACAF354ED49F23CB /* JSQMessagesAvatarImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 204DCC14B462F4372C1302E1C17E6416 /* JSQMessagesAvatarImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; F5B3429CF04A1E5E34323F804B49ABAD /* JSQMessagesMediaViewBubbleImageMasker.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EA94DB027B9E09C11C0A8CF03E3301E /* JSQMessagesMediaViewBubbleImageMasker.h */; settings = {ATTRIBUTES = (Public, ); }; }; F9D409B2DC4E229789F752C598E9BB84 /* JSQMessagesTypingView.h in Headers */ = {isa = PBXBuildFile; fileRef = F9A6E5018F0982EFD9F1DFB7EFC0A0CF /* JSQMessagesTypingView.h */; settings = {ATTRIBUTES = (Public, ); }; }; FC31582C351A0712001FFD233EFDBE1F /* JSQMessagesCollectionViewFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 45DFB4625C491C220C9095C8924AF516 /* JSQMessagesCollectionViewFlowLayout.m */; }; FEC2C524171149B1B72AB053D6F93781 /* JSQMessageData.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E54A32E3606D28BB7B28B8D6B327B8F /* JSQMessageData.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 0A8F278BF4A7E20DABC9CD44FF2F9603 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; proxyType = 1; remoteGlobalIDString = F4652DD49CBC129355726E669F81872C; remoteInfo = JSQMessagesViewController; }; 933388DAA89AAAA6AF988B7B798B1702 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; proxyType = 1; remoteGlobalIDString = F4652DD49CBC129355726E669F81872C; remoteInfo = JSQMessagesViewController; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 000C5E7A807ADBFE2D79B69B99AFDD3F /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; }; 057981684A21DCBF08FCF9696AF5A257 /* JSQMessageBubbleImageDataSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessageBubbleImageDataSource.h; sourceTree = ""; }; 09FC3F9B3A90D7404CBFA024E233743D /* Pods-SwiftExample-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-SwiftExample-dummy.m"; sourceTree = ""; }; 0B17F85F78315AB1381877780833F28C /* UIColor+JSQMessages.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UIColor+JSQMessages.h"; sourceTree = ""; }; 0D24203D45F99479F6F517E995BB95D8 /* JSQMessagesComposerTextView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesComposerTextView.m; sourceTree = ""; }; 0F5B8A4363DBAD37018BEEF6F27B82FF /* JSQMessagesLoadEarlierHeaderView.xib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.xib; path = JSQMessagesLoadEarlierHeaderView.xib; sourceTree = ""; }; 0F85995D44A1EFB944B78A3E5D540ACD /* Pods-SwiftExampleTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-SwiftExampleTests.modulemap"; sourceTree = ""; }; 11E7A34075E540D6F0C1816369F6A553 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; }; 15C66A457A812FA48828EC711F860B7D /* JSQMessageAvatarImageDataSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessageAvatarImageDataSource.h; sourceTree = ""; }; 16D8F43C9B0B02AB9CBC15639B532ED6 /* JSQMessagesMediaViewBubbleImageMasker.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaViewBubbleImageMasker.m; sourceTree = ""; }; 181EC36DD60BF893E063F12833392833 /* JSQAudioMediaViewAttributes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQAudioMediaViewAttributes.h; sourceTree = ""; }; 18A1292EAFE8E0D3CF56C41FEDDF665E /* JSQMessagesLabel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesLabel.h; sourceTree = ""; }; 1A03BEBEA2CF4310CF1B4DD56BF8F61E /* JSQMessagesToolbarContentView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesToolbarContentView.m; sourceTree = ""; }; 1A5053CFA2A987B6E737E6ACC79A3E3F /* JSQMessagesViewAccessoryButtonDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesViewAccessoryButtonDelegate.h; sourceTree = ""; }; 1C8C570836044007BF20B0ED2FBCFE2A /* JSQMessagesBubbleImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubbleImage.h; sourceTree = ""; }; 1D9EF71778372A1A307E9B873AB23EC5 /* Pods_SwiftExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1E54A32E3606D28BB7B28B8D6B327B8F /* JSQMessageData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessageData.h; sourceTree = ""; }; 1EA94DB027B9E09C11C0A8CF03E3301E /* JSQMessagesMediaViewBubbleImageMasker.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesMediaViewBubbleImageMasker.h; sourceTree = ""; }; 204DCC14B462F4372C1302E1C17E6416 /* JSQMessagesAvatarImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesAvatarImage.h; sourceTree = ""; }; 21DA795BA41ECCF30AFC0974C6EEFC85 /* JSQMessagesBubblesSizeCalculator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubblesSizeCalculator.m; sourceTree = ""; }; 22396D722DC9FA121DE6D85412F12FBD /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 270A64B26DAEE6176110C90558CA5A04 /* JSQMessagesInputToolbar.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesInputToolbar.h; sourceTree = ""; }; 297A5235136DACC67C3D66D02EA8ACB4 /* JSQMessagesCollectionViewCellIncoming.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewCellIncoming.h; sourceTree = ""; }; 2DBFA6E2DFD17C5DA8BD9A693E1ECC21 /* JSQMessagesAvatarImageFactory.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesAvatarImageFactory.h; sourceTree = ""; }; 3183A5D6D9FC13F7B60C5CD53898A57F /* JSQMessagesMediaPlaceholderView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaPlaceholderView.m; sourceTree = ""; }; 31B8479763A7110163F0F84342B0539C /* JSQPhotoMediaItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQPhotoMediaItem.m; sourceTree = ""; }; 34F57E9E440C5CD869A78C8C22056B25 /* JSQMessagesInputToolbar.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesInputToolbar.m; sourceTree = ""; }; 36A9B85D5390598A5B8CC766DF5F4E8E /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewFlowLayoutInvalidationContext.m; sourceTree = ""; }; 39C682EB43B30B56CCB58A160AB0FD22 /* Pods-SwiftExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftExampleTests.release.xcconfig"; sourceTree = ""; }; 3C49A9F16A1D7A83619623B722B24E00 /* JSQMessagesAvatarImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesAvatarImage.m; sourceTree = ""; }; 403BEDD017FB77FDC4E83A5601C64A0B /* UIView+JSQMessages.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UIView+JSQMessages.h"; sourceTree = ""; }; 4589B4FCDB9057D5AB4780BBF1161600 /* JSQMessagesViewController.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JSQMessagesViewController.xcconfig; sourceTree = ""; }; 45D48AC1E79D5721C30FA3231CB3B13B /* JSQMessagesCollectionViewCellOutgoing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewCellOutgoing.h; sourceTree = ""; }; 45DFB4625C491C220C9095C8924AF516 /* JSQMessagesCollectionViewFlowLayout.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewFlowLayout.m; sourceTree = ""; }; 4917A500131F941CFC3B7E20FBDB42CF /* JSQMessagesViewController-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JSQMessagesViewController-prefix.pch"; sourceTree = ""; }; 4B1A855B29CB4B0BD12E9320DAD300B9 /* JSQMessagesViewController.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = JSQMessagesViewController.modulemap; sourceTree = ""; }; 4B50D70959F21FC83E3BF23C39EB8823 /* UIImage+JSQMessages.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UIImage+JSQMessages.h"; sourceTree = ""; }; 4D939DED94CC13583A52A8202486FEA4 /* JSQMessagesTimestampFormatter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesTimestampFormatter.h; sourceTree = ""; }; 4EA4A228E463A5D1E3FB740DA14001D0 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; }; 4F7B63B0F90D2D277319508F3F66267E /* JSQMessagesLoadEarlierHeaderView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesLoadEarlierHeaderView.m; sourceTree = ""; }; 4FED1F4D658C702CD3C0CD427CDFF0BE /* JSQMessagesTimestampFormatter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesTimestampFormatter.m; sourceTree = ""; }; 524E6097931B7ED772C33D9543B0CF15 /* JSQMessage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessage.h; sourceTree = ""; }; 57FEE5C15DC269FDEF6AAE707DEFBCF1 /* JSQLocationMediaItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQLocationMediaItem.h; sourceTree = ""; }; 59AB2F73B47A5B9126EE502DD53AE8F2 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5C5E27903DA2690AF3381606582325D1 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewFlowLayoutInvalidationContext.h; sourceTree = ""; }; 5F1CB8272344A931595EEDD067164659 /* JSQMessagesAssets.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; path = JSQMessagesAssets.bundle; sourceTree = ""; }; 5F45B3B5C785B02B1CCEB51ED7087874 /* JSQMessagesCollectionViewCellIncoming.xib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.xib; path = JSQMessagesCollectionViewCellIncoming.xib; sourceTree = ""; }; 625934269CDEAD7D986818EBE8571EEC /* JSQMessagesCollectionViewCell.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCell.m; sourceTree = ""; }; 662E954CA5C9638FF6306DA2D3999D5D /* Pods-SwiftExampleTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-SwiftExampleTests-acknowledgements.markdown"; sourceTree = ""; }; 68F2C50C784ACCCAFC21D9C1E6A8F2D3 /* JSQLocationMediaItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQLocationMediaItem.m; sourceTree = ""; }; 70F9F509B25C0A3871DC1B80481296BE /* JSQMessagesBubblesSizeCalculator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubblesSizeCalculator.h; sourceTree = ""; }; 76244FCF63EFB83275D112B57D45F690 /* JSQMessagesTypingIndicatorFooterView.xib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.xib; path = JSQMessagesTypingIndicatorFooterView.xib; sourceTree = ""; }; 76633CAF877874C0C5EDCAE536B689A2 /* JSQPhotoMediaItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQPhotoMediaItem.h; sourceTree = ""; }; 7811EEFD769234A5BE01FC929C1E16E2 /* JSQMessageMediaData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessageMediaData.h; sourceTree = ""; }; 78B5DD2C75F208278D9E7C69E207EB84 /* Pods-SwiftExampleTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-SwiftExampleTests-umbrella.h"; sourceTree = ""; }; 7DADDDF43426A194DE729EB043B05B40 /* JSQAudioMediaItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQAudioMediaItem.m; sourceTree = ""; }; 836783FE72029591ED844FA76BB6E28E /* JSQMessagesBubbleImageFactory.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubbleImageFactory.h; sourceTree = ""; }; 8518A94198081D09F20092C6ED6D0ECE /* JSQMessagesViewController-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JSQMessagesViewController-umbrella.h"; sourceTree = ""; }; 8CE60A4BE154D1FC51F8DA103E7B021B /* JSQMediaItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMediaItem.m; sourceTree = ""; }; 8E6EA357877CB6D75E6B3C09A9E1056C /* Pods-SwiftExample-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftExample-resources.sh"; sourceTree = ""; }; 8EF5F4DAE2096A1664B32E754587FC81 /* JSQMessagesViewController.xib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.xib; path = JSQMessagesViewController.xib; sourceTree = ""; }; 8EFEB1A2B305346C484DCC7F5945E742 /* JSQMessagesTypingIndicatorFooterView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesTypingIndicatorFooterView.h; sourceTree = ""; }; 90382D314B6C88846052740703B80BE8 /* Pods-SwiftExampleTests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftExampleTests-frameworks.sh"; sourceTree = ""; }; 90AEE1B44E6580033A25A89E0ED45E53 /* JSQMessagesViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesViewController.h; sourceTree = ""; }; 91929344924005C6DCDD10292B0806B0 /* Pods-SwiftExampleTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-SwiftExampleTests-dummy.m"; sourceTree = ""; }; 9384C9933B633AE3D832A47B6BB1A5DB /* Pods-SwiftExample-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SwiftExample-acknowledgements.plist"; sourceTree = ""; }; 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 952379E223739B709AB0FA1E3ADEBFB8 /* UIColor+JSQMessages.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "UIColor+JSQMessages.m"; sourceTree = ""; }; 954B5BBA1444969ABE42B4BBC95DBE4A /* JSQMessagesMediaPlaceholderView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesMediaPlaceholderView.h; sourceTree = ""; }; 96A13D464D35831CD34646417886B824 /* JSQMessagesAvatarImageFactory.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesAvatarImageFactory.m; sourceTree = ""; }; 97F9F2DE8856199D3C36743C6049350E /* JSQVideoMediaItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQVideoMediaItem.h; sourceTree = ""; }; 9CA4F3FC4154CEFD439757AD85FB8FF0 /* JSQMessagesCollectionViewDelegateFlowLayout.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewDelegateFlowLayout.h; sourceTree = ""; }; 9DA3D277828916C0263FA41AD20CDBE0 /* JSQMessagesCollectionViewLayoutAttributes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewLayoutAttributes.m; sourceTree = ""; }; 9F7D42FB4AEE0C3244717BFD98C4F402 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; A107B4BA3E03390A14CDBB9E29604105 /* UIView+JSQMessages.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "UIView+JSQMessages.m"; sourceTree = ""; }; A153263CAA2EAE331530D8D20D432E0E /* JSQMessagesToolbarButtonFactory.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesToolbarButtonFactory.m; sourceTree = ""; }; AC7AAD02CE8BF396DF3DE2690B7BADB9 /* JSQMessagesCollectionView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionView.m; sourceTree = ""; }; AC7DEECF66B9FDEB1DAD70299286263D /* JSQMessagesCollectionViewCellOutgoing.xib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.xib; path = JSQMessagesCollectionViewCellOutgoing.xib; sourceTree = ""; }; ADAB1228EC10B7CB3572CF396510796A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; B11D3AC96762A7BB8EE32D0708706561 /* JSQMessagesToolbarContentView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesToolbarContentView.h; sourceTree = ""; }; B3D0F109A53EDB30FF0D5F4658B63579 /* JSQMessagesLabel.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesLabel.m; sourceTree = ""; }; B8B84FA61D0E6F92976F225FC571ED64 /* JSQMessagesBubbleSizeCalculating.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesBubbleSizeCalculating.h; sourceTree = ""; }; B8C4A3A79FF0482FD0C11FAE084DC372 /* JSQMessagesCellTextView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCellTextView.m; sourceTree = ""; }; B9D05ABBE014A589E50C4DD3079147C7 /* JSQMessagesCollectionViewFlowLayout.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewFlowLayout.h; sourceTree = ""; }; BAA121A98E3E27A7E633EA10BF84D0F6 /* Pods-SwiftExample-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftExample-frameworks.sh"; sourceTree = ""; }; BB2010E848C23731711B6C75E7EC534F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; BBDD43C084AE9FDC684D5DABD25C6F1A /* Pods_SwiftExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BCB1B6347793109A05327191F2CF1879 /* NSString+JSQMessages.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "NSString+JSQMessages.m"; sourceTree = ""; }; BD0F1BD625BC8FEB3B28C34F3AB4E928 /* Pods-SwiftExampleTests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftExampleTests-resources.sh"; sourceTree = ""; }; BEEAAF41A83046C91065CB2B58E11116 /* Pods-SwiftExample-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-SwiftExample-acknowledgements.markdown"; sourceTree = ""; }; BF99EBBA15C1C022348AA51C14598028 /* JSQMessagesViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JSQMessagesViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C0343B4EB8D3C3448E9916FC309DB2C9 /* JSQMessagesViewController-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "JSQMessagesViewController-dummy.m"; sourceTree = ""; }; C1C01EE99D250147B553333270006D40 /* JSQMessagesCollectionView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionView.h; sourceTree = ""; }; C388B48A95ECC8C3DE5E161C2A342C7F /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C426EB071A2AAFB1B3F35B3D93255033 /* JSQVideoMediaItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQVideoMediaItem.m; sourceTree = ""; }; C45B9EB1A4049C5465F2AAD260B904EE /* JSQAudioMediaItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQAudioMediaItem.h; sourceTree = ""; }; C5E9CABA925AABE167971A3758BEF1F3 /* Pods-SwiftExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftExampleTests.debug.xcconfig"; sourceTree = ""; }; C83404BD601FB59DCB478B4806474BCF /* JSQMessagesCollectionViewCellIncoming.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCellIncoming.m; sourceTree = ""; }; CCBDAC7B94A40319B10444540CB5761B /* JSQMessagesTypingIndicatorFooterView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesTypingIndicatorFooterView.m; sourceTree = ""; }; D057A513023775354F80A8D6F2A693CE /* Pods-SwiftExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftExample.debug.xcconfig"; sourceTree = ""; }; D350A894D316D0D75E96C1BEF67165D9 /* JSQMediaItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMediaItem.h; sourceTree = ""; }; D676EADFD0431F936F770C6D7EBD6747 /* JSQMessagesLoadEarlierHeaderView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesLoadEarlierHeaderView.h; sourceTree = ""; }; D7360DA9FBCFFA0FDB1999596B4931CE /* JSQMessagesToolbarContentView.xib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.xib; path = JSQMessagesToolbarContentView.xib; sourceTree = ""; }; D7A8676B26883007503D2860E45681F8 /* Pods-SwiftExample.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-SwiftExample.modulemap"; sourceTree = ""; }; D7AA00D128FAB1CFF7126516DCF234A6 /* NSBundle+JSQMessages.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+JSQMessages.m"; sourceTree = ""; }; D9FE093244C242BBC50C60B6C2408904 /* JSQMessagesTypingView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesTypingView.m; sourceTree = ""; }; DBC221A7F5EA869AABD19B4DBA4794A3 /* JSQMessagesViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesViewController.m; sourceTree = ""; }; DD2E9D8E624CB491CFA05C605A6BAE58 /* NSString+JSQMessages.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "NSString+JSQMessages.h"; sourceTree = ""; }; DDE72C64C54668D5527A9FDEF1A1008B /* UIImage+JSQMessages.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "UIImage+JSQMessages.m"; sourceTree = ""; }; DE4241BAE287FC44CA02BE8D83C65D0F /* JSQMessagesToolbarButtonFactory.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesToolbarButtonFactory.h; sourceTree = ""; }; DFE2D283BE74583997BA411C0BD33988 /* JSQMessagesBubbleImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubbleImage.m; sourceTree = ""; }; E007153261CE1297038828333F02E491 /* JSQMessages.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessages.h; sourceTree = ""; }; E17F4220992E5BD3FC797A1D7D2DFF72 /* JSQMessagesComposerTextView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesComposerTextView.h; sourceTree = ""; }; E69951F70517FF9E9ED57B837A31BDB1 /* JSQMessagesCellTextView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCellTextView.h; sourceTree = ""; }; E6E6F5B09B62665CE9FDE461A6E43BC2 /* JSQMessagesCollectionViewLayoutAttributes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewLayoutAttributes.h; sourceTree = ""; }; E8549B6714C5E269960CFF6FF7F67783 /* Pods-SwiftExampleTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SwiftExampleTests-acknowledgements.plist"; sourceTree = ""; }; EDA523A8EB08F4D0F668C6C519714FB8 /* Pods-SwiftExample-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-SwiftExample-umbrella.h"; sourceTree = ""; }; F04AA4E81635D54CC9DAC94484F2AF41 /* Pods-SwiftExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftExample.release.xcconfig"; sourceTree = ""; }; F13184B4EDAA1DA57A10BA8BE02A0C4D /* JSQMessagesCollectionViewCellOutgoing.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCellOutgoing.m; sourceTree = ""; }; F25BDE5C7405E48A57A7831625D551CD /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; }; F4D093DDB60343309422EFEEE9208A97 /* NSBundle+JSQMessages.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "NSBundle+JSQMessages.h"; sourceTree = ""; }; F6AAD2332EC33082F93D3D22255524A2 /* JSQMessagesCollectionViewCell.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewCell.h; sourceTree = ""; }; F968E895B19778B91D72E71FFF217B27 /* JSQAudioMediaViewAttributes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQAudioMediaViewAttributes.m; sourceTree = ""; }; F9A6E5018F0982EFD9F1DFB7EFC0A0CF /* JSQMessagesTypingView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesTypingView.h; sourceTree = ""; }; FB651C0ABF8FEE00B5A7178CE4200D2B /* JSQMessagesCollectionViewDataSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewDataSource.h; sourceTree = ""; }; FBAF3B9685F118DCE6F73267E8184C0B /* JSQMessagesBubbleImageFactory.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesBubbleImageFactory.m; sourceTree = ""; }; FDDCDD7D41BF7D9167B90EE90F87DA01 /* JSQMessage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = JSQMessage.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 6A375EC470937BE32201F83109DD269D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3594E2AFC955F5ADD6AA0CE636733F90 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 8993A5C075F1EA4D50D31FB969969530 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 5B20E77C78ECFFBEE94E631C6D48B3A9 /* AVFoundation.framework in Frameworks */, F10BB39915DF7FD797B9406F64D33942 /* CoreGraphics.framework in Frameworks */, 4221D9E061C1B880ABF28D86EF71A651 /* CoreLocation.framework in Frameworks */, 02B533E0835FB35880D73A0E77F7FF06 /* Foundation.framework in Frameworks */, 86285D5790B0FD08488082C749437BFC /* MapKit.framework in Frameworks */, 8DABA45AE25BA10088F6BA79B9895B37 /* MobileCoreServices.framework in Frameworks */, A728F75F1D2CAC09027F6D49D88467C0 /* QuartzCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; B49207F775F8DC11063D8E61DDC4BEB6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 8FD252B8B21CF2428E2A4E82AE1634A0 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1A18684CEE804EFBEC9A8BD22FC3B7C4 /* Controllers */ = { isa = PBXGroup; children = ( 90AEE1B44E6580033A25A89E0ED45E53 /* JSQMessagesViewController.h */, DBC221A7F5EA869AABD19B4DBA4794A3 /* JSQMessagesViewController.m */, ); path = Controllers; sourceTree = ""; }; 291211176730D891369C42C48E0496FE /* Views */ = { isa = PBXGroup; children = ( 5F45B3B5C785B02B1CCEB51ED7087874 /* JSQMessagesCollectionViewCellIncoming.xib */, AC7DEECF66B9FDEB1DAD70299286263D /* JSQMessagesCollectionViewCellOutgoing.xib */, 0F5B8A4363DBAD37018BEEF6F27B82FF /* JSQMessagesLoadEarlierHeaderView.xib */, D7360DA9FBCFFA0FDB1999596B4931CE /* JSQMessagesToolbarContentView.xib */, 76244FCF63EFB83275D112B57D45F690 /* JSQMessagesTypingIndicatorFooterView.xib */, ); path = Views; sourceTree = ""; }; 3712438C460693C0BDBA351720F11B4C /* Pods-SwiftExampleTests */ = { isa = PBXGroup; children = ( C388B48A95ECC8C3DE5E161C2A342C7F /* Info.plist */, 0F85995D44A1EFB944B78A3E5D540ACD /* Pods-SwiftExampleTests.modulemap */, 662E954CA5C9638FF6306DA2D3999D5D /* Pods-SwiftExampleTests-acknowledgements.markdown */, E8549B6714C5E269960CFF6FF7F67783 /* Pods-SwiftExampleTests-acknowledgements.plist */, 91929344924005C6DCDD10292B0806B0 /* Pods-SwiftExampleTests-dummy.m */, 90382D314B6C88846052740703B80BE8 /* Pods-SwiftExampleTests-frameworks.sh */, BD0F1BD625BC8FEB3B28C34F3AB4E928 /* Pods-SwiftExampleTests-resources.sh */, 78B5DD2C75F208278D9E7C69E207EB84 /* Pods-SwiftExampleTests-umbrella.h */, C5E9CABA925AABE167971A3758BEF1F3 /* Pods-SwiftExampleTests.debug.xcconfig */, 39C682EB43B30B56CCB58A160AB0FD22 /* Pods-SwiftExampleTests.release.xcconfig */, ); name = "Pods-SwiftExampleTests"; path = "Target Support Files/Pods-SwiftExampleTests"; sourceTree = ""; }; 39784F758495B83DFC74C28A12D801B5 /* Controllers */ = { isa = PBXGroup; children = ( 8EF5F4DAE2096A1664B32E754587FC81 /* JSQMessagesViewController.xib */, ); path = Controllers; sourceTree = ""; }; 3C58A0AFA4F102EBF0A34C666FEB40DE /* Resources */ = { isa = PBXGroup; children = ( B5CC86CA90B0AE1ADB6CC6E05ACBC6B9 /* JSQMessagesViewController */, ); name = Resources; sourceTree = ""; }; 6F169D9482A08C82540763A9A9E323C7 /* Factories */ = { isa = PBXGroup; children = ( 2DBFA6E2DFD17C5DA8BD9A693E1ECC21 /* JSQMessagesAvatarImageFactory.h */, 96A13D464D35831CD34646417886B824 /* JSQMessagesAvatarImageFactory.m */, 836783FE72029591ED844FA76BB6E28E /* JSQMessagesBubbleImageFactory.h */, FBAF3B9685F118DCE6F73267E8184C0B /* JSQMessagesBubbleImageFactory.m */, 1EA94DB027B9E09C11C0A8CF03E3301E /* JSQMessagesMediaViewBubbleImageMasker.h */, 16D8F43C9B0B02AB9CBC15639B532ED6 /* JSQMessagesMediaViewBubbleImageMasker.m */, 4D939DED94CC13583A52A8202486FEA4 /* JSQMessagesTimestampFormatter.h */, 4FED1F4D658C702CD3C0CD427CDFF0BE /* JSQMessagesTimestampFormatter.m */, DE4241BAE287FC44CA02BE8D83C65D0F /* JSQMessagesToolbarButtonFactory.h */, A153263CAA2EAE331530D8D20D432E0E /* JSQMessagesToolbarButtonFactory.m */, ); path = Factories; sourceTree = ""; }; 71D8F07FFA4672999905EE27ACF58886 /* Targets Support Files */ = { isa = PBXGroup; children = ( 9B12FD5E43A96FCC90121469EE1497A4 /* Pods-SwiftExample */, 3712438C460693C0BDBA351720F11B4C /* Pods-SwiftExampleTests */, ); name = "Targets Support Files"; sourceTree = ""; }; 7306E2DBAA8CED7A568ABDF8A1158343 /* Support Files */ = { isa = PBXGroup; children = ( 22396D722DC9FA121DE6D85412F12FBD /* Info.plist */, 4B1A855B29CB4B0BD12E9320DAD300B9 /* JSQMessagesViewController.modulemap */, 4589B4FCDB9057D5AB4780BBF1161600 /* JSQMessagesViewController.xcconfig */, C0343B4EB8D3C3448E9916FC309DB2C9 /* JSQMessagesViewController-dummy.m */, 4917A500131F941CFC3B7E20FBDB42CF /* JSQMessagesViewController-prefix.pch */, 8518A94198081D09F20092C6ED6D0ECE /* JSQMessagesViewController-umbrella.h */, ); name = "Support Files"; path = "SwiftExample/Pods/Target Support Files/JSQMessagesViewController"; sourceTree = ""; }; 7D8793ADA7FAC98EB26118A651EB6DCF /* Assets */ = { isa = PBXGroup; children = ( 5F1CB8272344A931595EEDD067164659 /* JSQMessagesAssets.bundle */, ); path = Assets; sourceTree = ""; }; 7DB346D0F39D3F0E887471402A8071AB = { isa = PBXGroup; children = ( 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */, AA40E395DE09ACA8469CC76EC1BA0DC1 /* Development Pods */, F4CDA5FA9197A41E0081E84F932906EB /* Frameworks */, B277277C13AEBF98FCF676394E09075B /* Products */, 71D8F07FFA4672999905EE27ACF58886 /* Targets Support Files */, ); sourceTree = ""; }; 84910C6D5BEA4C6B174481DBEAEE1EE6 /* Views */ = { isa = PBXGroup; children = ( E69951F70517FF9E9ED57B837A31BDB1 /* JSQMessagesCellTextView.h */, B8C4A3A79FF0482FD0C11FAE084DC372 /* JSQMessagesCellTextView.m */, C1C01EE99D250147B553333270006D40 /* JSQMessagesCollectionView.h */, AC7AAD02CE8BF396DF3DE2690B7BADB9 /* JSQMessagesCollectionView.m */, F6AAD2332EC33082F93D3D22255524A2 /* JSQMessagesCollectionViewCell.h */, 625934269CDEAD7D986818EBE8571EEC /* JSQMessagesCollectionViewCell.m */, 297A5235136DACC67C3D66D02EA8ACB4 /* JSQMessagesCollectionViewCellIncoming.h */, C83404BD601FB59DCB478B4806474BCF /* JSQMessagesCollectionViewCellIncoming.m */, 45D48AC1E79D5721C30FA3231CB3B13B /* JSQMessagesCollectionViewCellOutgoing.h */, F13184B4EDAA1DA57A10BA8BE02A0C4D /* JSQMessagesCollectionViewCellOutgoing.m */, E17F4220992E5BD3FC797A1D7D2DFF72 /* JSQMessagesComposerTextView.h */, 0D24203D45F99479F6F517E995BB95D8 /* JSQMessagesComposerTextView.m */, 270A64B26DAEE6176110C90558CA5A04 /* JSQMessagesInputToolbar.h */, 34F57E9E440C5CD869A78C8C22056B25 /* JSQMessagesInputToolbar.m */, 18A1292EAFE8E0D3CF56C41FEDDF665E /* JSQMessagesLabel.h */, B3D0F109A53EDB30FF0D5F4658B63579 /* JSQMessagesLabel.m */, D676EADFD0431F936F770C6D7EBD6747 /* JSQMessagesLoadEarlierHeaderView.h */, 4F7B63B0F90D2D277319508F3F66267E /* JSQMessagesLoadEarlierHeaderView.m */, 954B5BBA1444969ABE42B4BBC95DBE4A /* JSQMessagesMediaPlaceholderView.h */, 3183A5D6D9FC13F7B60C5CD53898A57F /* JSQMessagesMediaPlaceholderView.m */, B11D3AC96762A7BB8EE32D0708706561 /* JSQMessagesToolbarContentView.h */, 1A03BEBEA2CF4310CF1B4DD56BF8F61E /* JSQMessagesToolbarContentView.m */, 8EFEB1A2B305346C484DCC7F5945E742 /* JSQMessagesTypingIndicatorFooterView.h */, CCBDAC7B94A40319B10444540CB5761B /* JSQMessagesTypingIndicatorFooterView.m */, F9A6E5018F0982EFD9F1DFB7EFC0A0CF /* JSQMessagesTypingView.h */, D9FE093244C242BBC50C60B6C2408904 /* JSQMessagesTypingView.m */, ); path = Views; sourceTree = ""; }; 877018D6D26A7807A73C9312B0D91693 /* Categories */ = { isa = PBXGroup; children = ( F4D093DDB60343309422EFEEE9208A97 /* NSBundle+JSQMessages.h */, D7AA00D128FAB1CFF7126516DCF234A6 /* NSBundle+JSQMessages.m */, DD2E9D8E624CB491CFA05C605A6BAE58 /* NSString+JSQMessages.h */, BCB1B6347793109A05327191F2CF1879 /* NSString+JSQMessages.m */, 0B17F85F78315AB1381877780833F28C /* UIColor+JSQMessages.h */, 952379E223739B709AB0FA1E3ADEBFB8 /* UIColor+JSQMessages.m */, 4B50D70959F21FC83E3BF23C39EB8823 /* UIImage+JSQMessages.h */, DDE72C64C54668D5527A9FDEF1A1008B /* UIImage+JSQMessages.m */, 403BEDD017FB77FDC4E83A5601C64A0B /* UIView+JSQMessages.h */, A107B4BA3E03390A14CDBB9E29604105 /* UIView+JSQMessages.m */, ); path = Categories; sourceTree = ""; }; 8A8B37EB3D2966F054622D21A4F226B5 /* Layout */ = { isa = PBXGroup; children = ( 181EC36DD60BF893E063F12833392833 /* JSQAudioMediaViewAttributes.h */, F968E895B19778B91D72E71FFF217B27 /* JSQAudioMediaViewAttributes.m */, B8B84FA61D0E6F92976F225FC571ED64 /* JSQMessagesBubbleSizeCalculating.h */, 70F9F509B25C0A3871DC1B80481296BE /* JSQMessagesBubblesSizeCalculator.h */, 21DA795BA41ECCF30AFC0974C6EEFC85 /* JSQMessagesBubblesSizeCalculator.m */, B9D05ABBE014A589E50C4DD3079147C7 /* JSQMessagesCollectionViewFlowLayout.h */, 45DFB4625C491C220C9095C8924AF516 /* JSQMessagesCollectionViewFlowLayout.m */, 5C5E27903DA2690AF3381606582325D1 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.h */, 36A9B85D5390598A5B8CC766DF5F4E8E /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m */, E6E6F5B09B62665CE9FDE461A6E43BC2 /* JSQMessagesCollectionViewLayoutAttributes.h */, 9DA3D277828916C0263FA41AD20CDBE0 /* JSQMessagesCollectionViewLayoutAttributes.m */, ); path = Layout; sourceTree = ""; }; 97FB204C633DD2BCBE12AABA5783B2EA /* Model */ = { isa = PBXGroup; children = ( C45B9EB1A4049C5465F2AAD260B904EE /* JSQAudioMediaItem.h */, 7DADDDF43426A194DE729EB043B05B40 /* JSQAudioMediaItem.m */, 57FEE5C15DC269FDEF6AAE707DEFBCF1 /* JSQLocationMediaItem.h */, 68F2C50C784ACCCAFC21D9C1E6A8F2D3 /* JSQLocationMediaItem.m */, D350A894D316D0D75E96C1BEF67165D9 /* JSQMediaItem.h */, 8CE60A4BE154D1FC51F8DA103E7B021B /* JSQMediaItem.m */, 524E6097931B7ED772C33D9543B0CF15 /* JSQMessage.h */, FDDCDD7D41BF7D9167B90EE90F87DA01 /* JSQMessage.m */, 15C66A457A812FA48828EC711F860B7D /* JSQMessageAvatarImageDataSource.h */, 057981684A21DCBF08FCF9696AF5A257 /* JSQMessageBubbleImageDataSource.h */, 1E54A32E3606D28BB7B28B8D6B327B8F /* JSQMessageData.h */, 7811EEFD769234A5BE01FC929C1E16E2 /* JSQMessageMediaData.h */, 204DCC14B462F4372C1302E1C17E6416 /* JSQMessagesAvatarImage.h */, 3C49A9F16A1D7A83619623B722B24E00 /* JSQMessagesAvatarImage.m */, 1C8C570836044007BF20B0ED2FBCFE2A /* JSQMessagesBubbleImage.h */, DFE2D283BE74583997BA411C0BD33988 /* JSQMessagesBubbleImage.m */, FB651C0ABF8FEE00B5A7178CE4200D2B /* JSQMessagesCollectionViewDataSource.h */, 9CA4F3FC4154CEFD439757AD85FB8FF0 /* JSQMessagesCollectionViewDelegateFlowLayout.h */, 1A5053CFA2A987B6E737E6ACC79A3E3F /* JSQMessagesViewAccessoryButtonDelegate.h */, 76633CAF877874C0C5EDCAE536B689A2 /* JSQPhotoMediaItem.h */, 31B8479763A7110163F0F84342B0539C /* JSQPhotoMediaItem.m */, 97F9F2DE8856199D3C36743C6049350E /* JSQVideoMediaItem.h */, C426EB071A2AAFB1B3F35B3D93255033 /* JSQVideoMediaItem.m */, ); path = Model; sourceTree = ""; }; 9B12FD5E43A96FCC90121469EE1497A4 /* Pods-SwiftExample */ = { isa = PBXGroup; children = ( 59AB2F73B47A5B9126EE502DD53AE8F2 /* Info.plist */, D7A8676B26883007503D2860E45681F8 /* Pods-SwiftExample.modulemap */, BEEAAF41A83046C91065CB2B58E11116 /* Pods-SwiftExample-acknowledgements.markdown */, 9384C9933B633AE3D832A47B6BB1A5DB /* Pods-SwiftExample-acknowledgements.plist */, 09FC3F9B3A90D7404CBFA024E233743D /* Pods-SwiftExample-dummy.m */, BAA121A98E3E27A7E633EA10BF84D0F6 /* Pods-SwiftExample-frameworks.sh */, 8E6EA357877CB6D75E6B3C09A9E1056C /* Pods-SwiftExample-resources.sh */, EDA523A8EB08F4D0F668C6C519714FB8 /* Pods-SwiftExample-umbrella.h */, D057A513023775354F80A8D6F2A693CE /* Pods-SwiftExample.debug.xcconfig */, F04AA4E81635D54CC9DAC94484F2AF41 /* Pods-SwiftExample.release.xcconfig */, ); name = "Pods-SwiftExample"; path = "Target Support Files/Pods-SwiftExample"; sourceTree = ""; }; AA40E395DE09ACA8469CC76EC1BA0DC1 /* Development Pods */ = { isa = PBXGroup; children = ( D77D701EFD3B56AD5F7550F7A94DA0F9 /* JSQMessagesViewController */, ); name = "Development Pods"; sourceTree = ""; }; B277277C13AEBF98FCF676394E09075B /* Products */ = { isa = PBXGroup; children = ( BF99EBBA15C1C022348AA51C14598028 /* JSQMessagesViewController.framework */, BBDD43C084AE9FDC684D5DABD25C6F1A /* Pods_SwiftExample.framework */, 1D9EF71778372A1A307E9B873AB23EC5 /* Pods_SwiftExampleTests.framework */, ); name = Products; sourceTree = ""; }; B5CC86CA90B0AE1ADB6CC6E05ACBC6B9 /* JSQMessagesViewController */ = { isa = PBXGroup; children = ( 7D8793ADA7FAC98EB26118A651EB6DCF /* Assets */, 39784F758495B83DFC74C28A12D801B5 /* Controllers */, 291211176730D891369C42C48E0496FE /* Views */, ); path = JSQMessagesViewController; sourceTree = ""; }; CE0AE8B56CAF1880676EB3309784251F /* JSQMessagesViewController */ = { isa = PBXGroup; children = ( E007153261CE1297038828333F02E491 /* JSQMessages.h */, 877018D6D26A7807A73C9312B0D91693 /* Categories */, 1A18684CEE804EFBEC9A8BD22FC3B7C4 /* Controllers */, 6F169D9482A08C82540763A9A9E323C7 /* Factories */, 8A8B37EB3D2966F054622D21A4F226B5 /* Layout */, 97FB204C633DD2BCBE12AABA5783B2EA /* Model */, 84910C6D5BEA4C6B174481DBEAEE1EE6 /* Views */, ); path = JSQMessagesViewController; sourceTree = ""; }; D77D701EFD3B56AD5F7550F7A94DA0F9 /* JSQMessagesViewController */ = { isa = PBXGroup; children = ( CE0AE8B56CAF1880676EB3309784251F /* JSQMessagesViewController */, 3C58A0AFA4F102EBF0A34C666FEB40DE /* Resources */, 7306E2DBAA8CED7A568ABDF8A1158343 /* Support Files */, ); name = JSQMessagesViewController; path = ../..; sourceTree = ""; }; E0249A22C6300A37629CD9CD582C5BB3 /* iOS */ = { isa = PBXGroup; children = ( F25BDE5C7405E48A57A7831625D551CD /* AVFoundation.framework */, BB2010E848C23731711B6C75E7EC534F /* CoreGraphics.framework */, 11E7A34075E540D6F0C1816369F6A553 /* CoreLocation.framework */, 9F7D42FB4AEE0C3244717BFD98C4F402 /* Foundation.framework */, 000C5E7A807ADBFE2D79B69B99AFDD3F /* MapKit.framework */, 4EA4A228E463A5D1E3FB740DA14001D0 /* MobileCoreServices.framework */, ADAB1228EC10B7CB3572CF396510796A /* QuartzCore.framework */, ); name = iOS; sourceTree = ""; }; F4CDA5FA9197A41E0081E84F932906EB /* Frameworks */ = { isa = PBXGroup; children = ( E0249A22C6300A37629CD9CD582C5BB3 /* iOS */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 17FBF3A1AD378C1609111A3D30005691 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 6EE0C10AB1CD73E4712704C5EBF36E06 /* JSQAudioMediaItem.h in Headers */, 7ADBA442C9BCC7F8EC3A0FBBA607E756 /* JSQAudioMediaViewAttributes.h in Headers */, CB19BD71402A2C12929D93C0C037DB3A /* JSQLocationMediaItem.h in Headers */, 0971D308ACAB2C1715C743E52DB67352 /* JSQMediaItem.h in Headers */, 1286F46765A948304134C1BE15E17849 /* JSQMessage.h in Headers */, 16A087B7FE52F9BAE08AA28C059C2218 /* JSQMessageAvatarImageDataSource.h in Headers */, 149907C7995310AE4ED9C04ECFFE50E8 /* JSQMessageBubbleImageDataSource.h in Headers */, FEC2C524171149B1B72AB053D6F93781 /* JSQMessageData.h in Headers */, 5C043F37E4D4417ECA44BCCB8C53E619 /* JSQMessageMediaData.h in Headers */, 36AE02087D184A7E56566950492D16F1 /* JSQMessages.h in Headers */, F47D16CE94FA55B9ACAF354ED49F23CB /* JSQMessagesAvatarImage.h in Headers */, CA92CE252B96DD84DB04AAA5D6618577 /* JSQMessagesAvatarImageFactory.h in Headers */, AB13B0284E39B5EA7FFA164DF8A3D9F0 /* JSQMessagesBubbleImage.h in Headers */, B6F229A7CC57770B714476B5A0E780FC /* JSQMessagesBubbleImageFactory.h in Headers */, B478066D191A1EC33BC88923ECDFACBD /* JSQMessagesBubbleSizeCalculating.h in Headers */, 2559B594BDFE7243E130FDDE5A998F3F /* JSQMessagesBubblesSizeCalculator.h in Headers */, E720AFD9BD8AB70A310818414B616037 /* JSQMessagesCellTextView.h in Headers */, 4B2B428728D8129A8C18685DDD777516 /* JSQMessagesCollectionView.h in Headers */, 4C34FFA14C2A67491C11A75438A97B80 /* JSQMessagesCollectionViewCell.h in Headers */, E089B530BE8AC615FC2BC0355B8B0D42 /* JSQMessagesCollectionViewCellIncoming.h in Headers */, 7D4C9DD82DE39240C76624031C3DCADE /* JSQMessagesCollectionViewCellOutgoing.h in Headers */, AFA9BE3313A21908984FB78302B83DAE /* JSQMessagesCollectionViewDataSource.h in Headers */, 97FEE57845D475946B27A4CE1A6104FC /* JSQMessagesCollectionViewDelegateFlowLayout.h in Headers */, 71DC559E2BA0D17AE8E64CD561F408DB /* JSQMessagesCollectionViewFlowLayout.h in Headers */, 76A7469A8CC7AA4E71935F49DA02D91B /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.h in Headers */, F3202A58AC1881E5A2CDD748617E1625 /* JSQMessagesCollectionViewLayoutAttributes.h in Headers */, 94277C8660644DF12841EB2529FB8EF3 /* JSQMessagesComposerTextView.h in Headers */, 6478969CD4A1066DFB5300FF48566E13 /* JSQMessagesInputToolbar.h in Headers */, 95CF1A48AFF625E3052FAEF7BBFC049D /* JSQMessagesLabel.h in Headers */, DC79DE76128A819B15C8E6BB9908CE18 /* JSQMessagesLoadEarlierHeaderView.h in Headers */, 944099AD67D13A4371F74124C9AADE3A /* JSQMessagesMediaPlaceholderView.h in Headers */, F5B3429CF04A1E5E34323F804B49ABAD /* JSQMessagesMediaViewBubbleImageMasker.h in Headers */, 2497CF2AA7934A786AAF2D10FCD895CC /* JSQMessagesTimestampFormatter.h in Headers */, 2C1E614E1443B6DDC58ADC0D086DE7D1 /* JSQMessagesToolbarButtonFactory.h in Headers */, 99CFD1B36B9F7FC889B5507813B46D4F /* JSQMessagesToolbarContentView.h in Headers */, 4327D100E30A840BE1270082EC81DAEB /* JSQMessagesTypingIndicatorFooterView.h in Headers */, F9D409B2DC4E229789F752C598E9BB84 /* JSQMessagesTypingView.h in Headers */, B4B9EE079397BE229ED18E5E0B65DAE4 /* JSQMessagesViewAccessoryButtonDelegate.h in Headers */, EF73B5848C16E35AA68AE5125B8F10BB /* JSQMessagesViewController-umbrella.h in Headers */, CF5C4883DDFE68428FA6E73A6BFD05FD /* JSQMessagesViewController.h in Headers */, 6D57B1EF4E9995010BBC9151D6D431E7 /* JSQPhotoMediaItem.h in Headers */, B8FB0FC4261F6B329F2EFE2534A3ADBA /* JSQVideoMediaItem.h in Headers */, 42D2BD077069B8043B6B1DF29FE0B769 /* NSBundle+JSQMessages.h in Headers */, 7142F1E3299EA9EFA1C84A1E3312393E /* NSString+JSQMessages.h in Headers */, A0F7CFFAB11AFFE78899BB963AF651DB /* UIColor+JSQMessages.h in Headers */, C7C143A2A75203893FCB3F984D5021A8 /* UIImage+JSQMessages.h in Headers */, 973EC98CBB971ABD8610C8117AD93803 /* UIView+JSQMessages.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 39A5CF49B5CB6D3104F6B6B5FB911009 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 83FCD6BA1C85F35E81EAC5A114A1711C /* Pods-SwiftExampleTests-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; AB0CF326E52F74D4716A7FF6BD573036 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 1E794C200B2DC30F9771FA2A99746C54 /* Pods-SwiftExample-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 78EB3C22625A986A82D46A10D4E155A9 /* Pods-SwiftExampleTests */ = { isa = PBXNativeTarget; buildConfigurationList = 6A03F344ED471784BD2E1B0B40FE1458 /* Build configuration list for PBXNativeTarget "Pods-SwiftExampleTests" */; buildPhases = ( BC67C03858271054C0C977B792890A83 /* Sources */, 6A375EC470937BE32201F83109DD269D /* Frameworks */, 39A5CF49B5CB6D3104F6B6B5FB911009 /* Headers */, ); buildRules = ( ); dependencies = ( D660908F1A605FE2EE178DD56870CCBF /* PBXTargetDependency */, ); name = "Pods-SwiftExampleTests"; productName = "Pods-SwiftExampleTests"; productReference = 1D9EF71778372A1A307E9B873AB23EC5 /* Pods_SwiftExampleTests.framework */; productType = "com.apple.product-type.framework"; }; AF775383F4A4D924FEBCA59F165114F3 /* Pods-SwiftExample */ = { isa = PBXNativeTarget; buildConfigurationList = BC858299533334ABBB6C897A5F28DC84 /* Build configuration list for PBXNativeTarget "Pods-SwiftExample" */; buildPhases = ( 34F28CB473F6BD156C729B3A1525B4FA /* Sources */, B49207F775F8DC11063D8E61DDC4BEB6 /* Frameworks */, AB0CF326E52F74D4716A7FF6BD573036 /* Headers */, ); buildRules = ( ); dependencies = ( 996AE8E7B5DB15924ACE55F93F24D98A /* PBXTargetDependency */, ); name = "Pods-SwiftExample"; productName = "Pods-SwiftExample"; productReference = BBDD43C084AE9FDC684D5DABD25C6F1A /* Pods_SwiftExample.framework */; productType = "com.apple.product-type.framework"; }; F4652DD49CBC129355726E669F81872C /* JSQMessagesViewController */ = { isa = PBXNativeTarget; buildConfigurationList = 26A248FEC0DC110B896C351FCEC58A3E /* Build configuration list for PBXNativeTarget "JSQMessagesViewController" */; buildPhases = ( C76D543036B3904A1BF1496466E21749 /* Sources */, 8993A5C075F1EA4D50D31FB969969530 /* Frameworks */, 17FBF3A1AD378C1609111A3D30005691 /* Headers */, 30A68B7DC6A86E8C970C4A2F97F74E89 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = JSQMessagesViewController; productName = JSQMessagesViewController; productReference = BF99EBBA15C1C022348AA51C14598028 /* JSQMessagesViewController.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ D41D8CD98F00B204E9800998ECF8427E /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0700; }; buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 7DB346D0F39D3F0E887471402A8071AB; productRefGroup = B277277C13AEBF98FCF676394E09075B /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F4652DD49CBC129355726E669F81872C /* JSQMessagesViewController */, AF775383F4A4D924FEBCA59F165114F3 /* Pods-SwiftExample */, 78EB3C22625A986A82D46A10D4E155A9 /* Pods-SwiftExampleTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 30A68B7DC6A86E8C970C4A2F97F74E89 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 31CA73CACCCA6864F1D2DEEC98B6BE64 /* JSQMessagesAssets.bundle in Resources */, 48033F047B7C8DAD4C57E33B9FFFCAC7 /* JSQMessagesCollectionViewCellIncoming.xib in Resources */, 1F73A87E86E4F06F65C24C8D706C3D06 /* JSQMessagesCollectionViewCellOutgoing.xib in Resources */, 57771709543EFBA38AEEC28E2916CE50 /* JSQMessagesLoadEarlierHeaderView.xib in Resources */, 09786FAEA00C46AE161738AA6769187E /* JSQMessagesToolbarContentView.xib in Resources */, A5ACA57BF20BD49AE7815646FBF2AEFC /* JSQMessagesTypingIndicatorFooterView.xib in Resources */, 3C7D2FDBBEB3EE1A79EABA1AE899E6E4 /* JSQMessagesViewController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 34F28CB473F6BD156C729B3A1525B4FA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 13702F016714682BE46AE7CF2FD69FA2 /* Pods-SwiftExample-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BC67C03858271054C0C977B792890A83 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E9D44B069CEE8F69CEF692B8B6F689C3 /* Pods-SwiftExampleTests-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; C76D543036B3904A1BF1496466E21749 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A89A6F15AC2DBAB06C84F9F83CE0A705 /* JSQAudioMediaItem.m in Sources */, 5146B25DDEE785B138DEBA9916625FF1 /* JSQAudioMediaViewAttributes.m in Sources */, D4E2E4076834A48E70FE2F62EB453B39 /* JSQLocationMediaItem.m in Sources */, A9DC2E25543160D5AE3048910F6889AB /* JSQMediaItem.m in Sources */, A032B0420BE66EC5CFBAA119A095D42D /* JSQMessage.m in Sources */, BFA9CF241AB70C4CF7A00A1973B0F754 /* JSQMessagesAvatarImage.m in Sources */, 0B73D8729FFE1530E2004AF2F2F1FAF5 /* JSQMessagesAvatarImageFactory.m in Sources */, EE33A0AEE5DE33EEEF5023E9F96E028C /* JSQMessagesBubbleImage.m in Sources */, 2208A31A32190C3B7A2BEC5249DE9573 /* JSQMessagesBubbleImageFactory.m in Sources */, 89988ADA3236378336D637CDC1705308 /* JSQMessagesBubblesSizeCalculator.m in Sources */, 8C4D07E6D5A864CB5165954C17DC199C /* JSQMessagesCellTextView.m in Sources */, 299BF0BD9436572EF05A8DD686426B38 /* JSQMessagesCollectionView.m in Sources */, 4CAFA12EA4BDAA9EF1C8346BA5AB346A /* JSQMessagesCollectionViewCell.m in Sources */, B8D33D0D75B4A3AE09DC7E36A1158D35 /* JSQMessagesCollectionViewCellIncoming.m in Sources */, 70E497827BBDDF1A6C3F1FE7AE096DA6 /* JSQMessagesCollectionViewCellOutgoing.m in Sources */, FC31582C351A0712001FFD233EFDBE1F /* JSQMessagesCollectionViewFlowLayout.m in Sources */, E15B1C6287EE20BD8EA7079D0A877FC5 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m in Sources */, EFDFB9712A563AFEC8C458516F5353A6 /* JSQMessagesCollectionViewLayoutAttributes.m in Sources */, 7F833206585AFA3C76D2CA6468F08C4C /* JSQMessagesComposerTextView.m in Sources */, 32CB2365204BAFC0BCD8FA1830845F17 /* JSQMessagesInputToolbar.m in Sources */, 5CA041E616D1BF0896F033ABB98337B7 /* JSQMessagesLabel.m in Sources */, 467F3A77A5336226B0DB8A7EA4826D11 /* JSQMessagesLoadEarlierHeaderView.m in Sources */, 19515B0DDD209485D2030C4CDD58861A /* JSQMessagesMediaPlaceholderView.m in Sources */, 45063CC65F42020D48F704FF2A319A27 /* JSQMessagesMediaViewBubbleImageMasker.m in Sources */, BD6D7350FDCE9ECD71CE64FD4D0A296F /* JSQMessagesTimestampFormatter.m in Sources */, C780E3A896590680ACE7C79078190E27 /* JSQMessagesToolbarButtonFactory.m in Sources */, A8107F45AA5D54B548D64490E09DCE76 /* JSQMessagesToolbarContentView.m in Sources */, C36557885745FB505AD57EF65EC4538F /* JSQMessagesTypingIndicatorFooterView.m in Sources */, 6DB64D96EE290E26615F277C26FB8BDD /* JSQMessagesTypingView.m in Sources */, 4D95177D6F2A51F045425E260D7D083D /* JSQMessagesViewController-dummy.m in Sources */, 7772024265D92D6412694019423F801C /* JSQMessagesViewController.m in Sources */, 51D1C525F7C4969E543A01CD17D44565 /* JSQPhotoMediaItem.m in Sources */, 4AD164814308A665D1F39A097358462B /* JSQVideoMediaItem.m in Sources */, 61FC3013A1F5826C8FDE76ECDFDF755C /* NSBundle+JSQMessages.m in Sources */, 618A6F67E2F5B3D799F2BE94168878F9 /* NSString+JSQMessages.m in Sources */, 0CCC464AF1B3662AF7FAC7C2DBB646A0 /* UIColor+JSQMessages.m in Sources */, CE52747511141C4E5ED4DFFA53CFEA09 /* UIImage+JSQMessages.m in Sources */, C91D96396EF540192BDD3D2334B95AEA /* UIView+JSQMessages.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 996AE8E7B5DB15924ACE55F93F24D98A /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = JSQMessagesViewController; target = F4652DD49CBC129355726E669F81872C /* JSQMessagesViewController */; targetProxy = 933388DAA89AAAA6AF988B7B798B1702 /* PBXContainerItemProxy */; }; D660908F1A605FE2EE178DD56870CCBF /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = JSQMessagesViewController; target = F4652DD49CBC129355726E669F81872C /* JSQMessagesViewController */; targetProxy = 0A8F278BF4A7E20DABC9CD44FF2F9603 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 034014829C1E4434983CF3A690C12017 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; ONLY_ACTIVE_ARCH = YES; STRIP_INSTALLED_PRODUCT = NO; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 59EB33A314F8B052D9EE81973D4E0C36 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = C5E9CABA925AABE167971A3758BEF1F3 /* Pods-SwiftExampleTests.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Target Support Files/Pods-SwiftExampleTests/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.modulemap"; MTL_ENABLE_DEBUG_INFO = YES; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = Pods_SwiftExampleTests; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 90A907A19BF0E7F5BF789F01C44BAD96 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4589B4FCDB9057D5AB4780BBF1161600 /* JSQMessagesViewController.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_PREFIX_HEADER = "Target Support Files/JSQMessagesViewController/JSQMessagesViewController-prefix.pch"; INFOPLIST_FILE = "Target Support Files/JSQMessagesViewController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = "Target Support Files/JSQMessagesViewController/JSQMessagesViewController.modulemap"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = JSQMessagesViewController; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 9A17C04E2F2412B62A7F30237A1CCE71 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = F04AA4E81635D54CC9DAC94484F2AF41 /* Pods-SwiftExample.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Target Support Files/Pods-SwiftExample/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-SwiftExample/Pods-SwiftExample.modulemap"; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = Pods_SwiftExample; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; AEF72D5DC19757CD6B3621AAAD9CDFD0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D057A513023775354F80A8D6F2A693CE /* Pods-SwiftExample.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Target Support Files/Pods-SwiftExample/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-SwiftExample/Pods-SwiftExample.modulemap"; MTL_ENABLE_DEBUG_INFO = YES; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = Pods_SwiftExample; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; C7E474A03CAE21F1CB6046E7344C59BD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; STRIP_INSTALLED_PRODUCT = NO; SYMROOT = "${SRCROOT}/../build"; VALIDATE_PRODUCT = YES; }; name = Release; }; CEF76AF0463E6053029C22049B591F36 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 39C682EB43B30B56CCB58A160AB0FD22 /* Pods-SwiftExampleTests.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Target Support Files/Pods-SwiftExampleTests/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.modulemap"; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = Pods_SwiftExampleTests; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; EA2E9DBAF8EE952D6AEC6510C9E9D5F5 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4589B4FCDB9057D5AB4780BBF1161600 /* JSQMessagesViewController.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_PREFIX_HEADER = "Target Support Files/JSQMessagesViewController/JSQMessagesViewController-prefix.pch"; INFOPLIST_FILE = "Target Support Files/JSQMessagesViewController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = "Target Support Files/JSQMessagesViewController/JSQMessagesViewController.modulemap"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = JSQMessagesViewController; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 26A248FEC0DC110B896C351FCEC58A3E /* Build configuration list for PBXNativeTarget "JSQMessagesViewController" */ = { isa = XCConfigurationList; buildConfigurations = ( 90A907A19BF0E7F5BF789F01C44BAD96 /* Debug */, EA2E9DBAF8EE952D6AEC6510C9E9D5F5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( 034014829C1E4434983CF3A690C12017 /* Debug */, C7E474A03CAE21F1CB6046E7344C59BD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6A03F344ED471784BD2E1B0B40FE1458 /* Build configuration list for PBXNativeTarget "Pods-SwiftExampleTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 59EB33A314F8B052D9EE81973D4E0C36 /* Debug */, CEF76AF0463E6053029C22049B591F36 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BC858299533334ABBB6C897A5F28DC84 /* Build configuration list for PBXNativeTarget "Pods-SwiftExample" */ = { isa = XCConfigurationList; buildConfigurations = ( AEF72D5DC19757CD6B3621AAAD9CDFD0 /* Debug */, 9A17C04E2F2412B62A7F30237A1CCE71 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; } ================================================ FILE: SwiftExample/Pods/Target Support Files/JSQMessagesViewController/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 7.3.4 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-dummy.m ================================================ #import @interface PodsDummy_JSQMessagesViewController : NSObject @end @implementation PodsDummy_JSQMessagesViewController @end ================================================ FILE: SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-umbrella.h ================================================ #import #import "NSBundle+JSQMessages.h" #import "NSString+JSQMessages.h" #import "UIColor+JSQMessages.h" #import "UIImage+JSQMessages.h" #import "UIView+JSQMessages.h" #import "JSQMessagesViewController.h" #import "JSQMessagesAvatarImageFactory.h" #import "JSQMessagesBubbleImageFactory.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" #import "JSQMessagesTimestampFormatter.h" #import "JSQMessagesToolbarButtonFactory.h" #import "JSQMessages.h" #import "JSQAudioMediaViewAttributes.h" #import "JSQMessagesBubbleSizeCalculating.h" #import "JSQMessagesBubblesSizeCalculator.h" #import "JSQMessagesCollectionViewFlowLayout.h" #import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h" #import "JSQMessagesCollectionViewLayoutAttributes.h" #import "JSQAudioMediaItem.h" #import "JSQLocationMediaItem.h" #import "JSQMediaItem.h" #import "JSQMessage.h" #import "JSQMessageAvatarImageDataSource.h" #import "JSQMessageBubbleImageDataSource.h" #import "JSQMessageData.h" #import "JSQMessageMediaData.h" #import "JSQMessagesAvatarImage.h" #import "JSQMessagesBubbleImage.h" #import "JSQMessagesCollectionViewDataSource.h" #import "JSQMessagesCollectionViewDelegateFlowLayout.h" #import "JSQMessagesViewAccessoryButtonDelegate.h" #import "JSQPhotoMediaItem.h" #import "JSQVideoMediaItem.h" #import "JSQMessagesCellTextView.h" #import "JSQMessagesCollectionView.h" #import "JSQMessagesCollectionViewCell.h" #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" #import "JSQMessagesComposerTextView.h" #import "JSQMessagesInputToolbar.h" #import "JSQMessagesLabel.h" #import "JSQMessagesLoadEarlierHeaderView.h" #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesToolbarContentView.h" #import "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesTypingView.h" FOUNDATION_EXPORT double JSQMessagesViewControllerVersionNumber; FOUNDATION_EXPORT const unsigned char JSQMessagesViewControllerVersionString[]; ================================================ FILE: SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController.modulemap ================================================ framework module JSQMessagesViewController { umbrella header "JSQMessagesViewController-umbrella.h" export * module * { export * } } ================================================ FILE: SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController.xcconfig ================================================ CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" OTHER_LDFLAGS = -framework "AVFoundation" -framework "CoreGraphics" -framework "CoreLocation" -framework "MapKit" -framework "MobileCoreServices" -framework "QuartzCore" PODS_BUILD_DIR = $BUILD_DIR PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-acknowledgements.markdown ================================================ # Acknowledgements This application makes use of the following third party libraries: ## JSQMessagesViewController MIT License Copyright (c) 2013-present Jesse Squires http://www.jessesquires.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Generated by CocoaPods - https://cocoapods.org ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-acknowledgements.plist ================================================ PreferenceSpecifiers FooterText This application makes use of the following third party libraries: Title Acknowledgements Type PSGroupSpecifier FooterText MIT License Copyright (c) 2013-present Jesse Squires http://www.jessesquires.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title JSQMessagesViewController Type PSGroupSpecifier FooterText Generated by CocoaPods - https://cocoapods.org Title Type PSGroupSpecifier StringsTable Acknowledgements Title Acknowledgements ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-dummy.m ================================================ #import @interface PodsDummy_Pods_SwiftExample : NSObject @end @implementation PodsDummy_Pods_SwiftExample @end ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-frameworks.sh ================================================ #!/bin/sh set -e echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" install_framework() { if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; then local source="$1" fi local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ -L "${source}" ]; then echo "Symlinked..." source="$(readlink "${source}")" fi # use filter instead of exclude so missing patterns dont' throw errors echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" local basename basename="$(basename -s .framework "$1")" binary="${destination}/${basename}.framework/${basename}" if ! [ -r "$binary" ]; then binary="${destination}/${basename}" fi # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then strip_invalid_archs "$binary" fi # Resign the code if required by the build settings to avoid unstable apps code_sign_if_enabled "${destination}/$(basename "$1")" # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then local swift_runtime_libs swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) for lib in $swift_runtime_libs; do echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" code_sign_if_enabled "${destination}/${lib}" done fi } # Signs a framework with the provided identity code_sign_if_enabled() { if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then # Use the current code_sign_identitiy echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\"" /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1" fi } # Strip invalid architectures strip_invalid_archs() { binary="$1" # Get architectures for current file archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" stripped="" for arch in $archs; do if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$binary" "$binary" || exit 1 stripped="$stripped $arch" fi done if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi } if [[ "$CONFIGURATION" == "Debug" ]]; then install_framework "$BUILT_PRODUCTS_DIR/JSQMessagesViewController/JSQMessagesViewController.framework" fi if [[ "$CONFIGURATION" == "Release" ]]; then install_framework "$BUILT_PRODUCTS_DIR/JSQMessagesViewController/JSQMessagesViewController.framework" fi ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-resources.sh ================================================ #!/bin/sh set -e mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt > "$RESOURCES_TO_COPY" XCASSET_FILES=() case "${TARGETED_DEVICE_FAMILY}" in 1,2) TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" ;; 1) TARGET_DEVICE_ARGS="--target-device iphone" ;; 2) TARGET_DEVICE_ARGS="--target-device ipad" ;; *) TARGET_DEVICE_ARGS="--target-device mac" ;; esac realpath() { DIRECTORY="$(cd "${1%/*}" && pwd)" FILENAME="${1##*/}" echo "$DIRECTORY/$FILENAME" } install_resource() { if [[ "$1" = /* ]] ; then RESOURCE_PATH="$1" else RESOURCE_PATH="${PODS_ROOT}/$1" fi if [[ ! -e "$RESOURCE_PATH" ]] ; then cat << EOM error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. EOM exit 1 fi case $RESOURCE_PATH in *.storyboard) echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} ;; *.xib) echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} ;; *.framework) echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" ;; *.xcdatamodel) echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" ;; *.xcdatamodeld) echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" ;; *.xcmappingmodel) echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" ;; *.xcassets) ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH") XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") ;; *) echo "$RESOURCE_PATH" echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" ;; esac } mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" fi rm -f "$RESOURCES_TO_COPY" if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] then # Find all other xcassets (this unfortunately includes those of path pods and other targets). OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) while read line; do if [[ $line != "`realpath $PODS_ROOT`*" ]]; then XCASSET_FILES+=("$line") fi done <<<"$OTHER_XCASSETS" printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" fi ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-umbrella.h ================================================ #import FOUNDATION_EXPORT double Pods_SwiftExampleVersionNumber; FOUNDATION_EXPORT const unsigned char Pods_SwiftExampleVersionString[]; ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.debug.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController/JSQMessagesViewController.framework/Headers" OTHER_LDFLAGS = $(inherited) -framework "JSQMessagesViewController" PODS_BUILD_DIR = $BUILD_DIR PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT}/Pods ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.modulemap ================================================ framework module Pods_SwiftExample { umbrella header "Pods-SwiftExample-umbrella.h" export * module * { export * } } ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.release.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController/JSQMessagesViewController.framework/Headers" OTHER_LDFLAGS = $(inherited) -framework "JSQMessagesViewController" PODS_BUILD_DIR = $BUILD_DIR PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT}/Pods ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-acknowledgements.markdown ================================================ # Acknowledgements This application makes use of the following third party libraries: ## JSQMessagesViewController MIT License Copyright (c) 2013-present Jesse Squires http://www.jessesquires.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Generated by CocoaPods - https://cocoapods.org ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-acknowledgements.plist ================================================ PreferenceSpecifiers FooterText This application makes use of the following third party libraries: Title Acknowledgements Type PSGroupSpecifier FooterText MIT License Copyright (c) 2013-present Jesse Squires http://www.jessesquires.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title JSQMessagesViewController Type PSGroupSpecifier FooterText Generated by CocoaPods - https://cocoapods.org Title Type PSGroupSpecifier StringsTable Acknowledgements Title Acknowledgements ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-dummy.m ================================================ #import @interface PodsDummy_Pods_SwiftExampleTests : NSObject @end @implementation PodsDummy_Pods_SwiftExampleTests @end ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-frameworks.sh ================================================ #!/bin/sh set -e echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" install_framework() { if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; then local source="$1" fi local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ -L "${source}" ]; then echo "Symlinked..." source="$(readlink "${source}")" fi # use filter instead of exclude so missing patterns dont' throw errors echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" local basename basename="$(basename -s .framework "$1")" binary="${destination}/${basename}.framework/${basename}" if ! [ -r "$binary" ]; then binary="${destination}/${basename}" fi # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then strip_invalid_archs "$binary" fi # Resign the code if required by the build settings to avoid unstable apps code_sign_if_enabled "${destination}/$(basename "$1")" # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then local swift_runtime_libs swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) for lib in $swift_runtime_libs; do echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" code_sign_if_enabled "${destination}/${lib}" done fi } # Signs a framework with the provided identity code_sign_if_enabled() { if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then # Use the current code_sign_identitiy echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\"" /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1" fi } # Strip invalid architectures strip_invalid_archs() { binary="$1" # Get architectures for current file archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" stripped="" for arch in $archs; do if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$binary" "$binary" || exit 1 stripped="$stripped $arch" fi done if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi } if [[ "$CONFIGURATION" == "Debug" ]]; then install_framework "$BUILT_PRODUCTS_DIR/JSQMessagesViewController/JSQMessagesViewController.framework" fi if [[ "$CONFIGURATION" == "Release" ]]; then install_framework "$BUILT_PRODUCTS_DIR/JSQMessagesViewController/JSQMessagesViewController.framework" fi ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-resources.sh ================================================ #!/bin/sh set -e mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt > "$RESOURCES_TO_COPY" XCASSET_FILES=() case "${TARGETED_DEVICE_FAMILY}" in 1,2) TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" ;; 1) TARGET_DEVICE_ARGS="--target-device iphone" ;; 2) TARGET_DEVICE_ARGS="--target-device ipad" ;; *) TARGET_DEVICE_ARGS="--target-device mac" ;; esac realpath() { DIRECTORY="$(cd "${1%/*}" && pwd)" FILENAME="${1##*/}" echo "$DIRECTORY/$FILENAME" } install_resource() { if [[ "$1" = /* ]] ; then RESOURCE_PATH="$1" else RESOURCE_PATH="${PODS_ROOT}/$1" fi if [[ ! -e "$RESOURCE_PATH" ]] ; then cat << EOM error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. EOM exit 1 fi case $RESOURCE_PATH in *.storyboard) echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} ;; *.xib) echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} ;; *.framework) echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" ;; *.xcdatamodel) echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" ;; *.xcdatamodeld) echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" ;; *.xcmappingmodel) echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" ;; *.xcassets) ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH") XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") ;; *) echo "$RESOURCE_PATH" echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" ;; esac } mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" fi rm -f "$RESOURCES_TO_COPY" if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] then # Find all other xcassets (this unfortunately includes those of path pods and other targets). OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) while read line; do if [[ $line != "`realpath $PODS_ROOT`*" ]]; then XCASSET_FILES+=("$line") fi done <<<"$OTHER_XCASSETS" printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" fi ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-umbrella.h ================================================ #import FOUNDATION_EXPORT double Pods_SwiftExampleTestsVersionNumber; FOUNDATION_EXPORT const unsigned char Pods_SwiftExampleTestsVersionString[]; ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.debug.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController/JSQMessagesViewController.framework/Headers" OTHER_LDFLAGS = $(inherited) -framework "JSQMessagesViewController" PODS_BUILD_DIR = $BUILD_DIR PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT}/Pods ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.modulemap ================================================ framework module Pods_SwiftExampleTests { umbrella header "Pods-SwiftExampleTests-umbrella.h" export * module * { export * } } ================================================ FILE: SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.release.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/JSQMessagesViewController/JSQMessagesViewController.framework/Headers" OTHER_LDFLAGS = $(inherited) -framework "JSQMessagesViewController" PODS_BUILD_DIR = $BUILD_DIR PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT}/Pods ================================================ FILE: SwiftExample/SwiftExample/AppDelegate.swift ================================================ // // AppDelegate.swift // SwiftExample // // Created by Dan Leonard on 5/8/16. // Copyright © 2016 MacMeDan. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: SwiftExample/SwiftExample/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: SwiftExample/SwiftExample/Base.lproj/Main.storyboard ================================================ ================================================ FILE: SwiftExample/SwiftExample/ChatViewController.swift ================================================ // // ChatViewController.swift // SwiftExample // // Created by Dan Leonard on 5/11/16. // Copyright © 2016 MacMeDan. All rights reserved. // import UIKit import JSQMessagesViewController class ChatViewController: JSQMessagesViewController { var messages = [JSQMessage]() let defaults = UserDefaults.standard var conversation: Conversation? var incomingBubble: JSQMessagesBubbleImage! var outgoingBubble: JSQMessagesBubbleImage! fileprivate var displayName: String! override func viewDidLoad() { super.viewDidLoad() // Setup navigation setupBackButton() /** * Override point: * * Example of how to cusomize the bubble appearence for incoming and outgoing messages. * Based on the Settings of the user display two differnent type of bubbles. * */ if defaults.bool(forKey: Setting.removeBubbleTails.rawValue) { // Make taillessBubbles incomingBubble = JSQMessagesBubbleImageFactory(bubble: UIImage.jsq_bubbleCompactTailless(), capInsets: UIEdgeInsets.zero, layoutDirection: UIApplication.shared.userInterfaceLayoutDirection).incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue()) outgoingBubble = JSQMessagesBubbleImageFactory(bubble: UIImage.jsq_bubbleCompactTailless(), capInsets: UIEdgeInsets.zero, layoutDirection: UIApplication.shared.userInterfaceLayoutDirection).outgoingMessagesBubbleImage(with: UIColor.lightGray) } else { // Bubbles with tails incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue()) outgoingBubble = JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImage(with: UIColor.lightGray) } /** * Example on showing or removing Avatars based on user settings. */ if defaults.bool(forKey: Setting.removeAvatar.rawValue) { collectionView?.collectionViewLayout.incomingAvatarViewSize = .zero collectionView?.collectionViewLayout.outgoingAvatarViewSize = .zero } else { collectionView?.collectionViewLayout.incomingAvatarViewSize = CGSize(width: kJSQMessagesCollectionViewAvatarSizeDefault, height:kJSQMessagesCollectionViewAvatarSizeDefault ) collectionView?.collectionViewLayout.outgoingAvatarViewSize = CGSize(width: kJSQMessagesCollectionViewAvatarSizeDefault, height:kJSQMessagesCollectionViewAvatarSizeDefault ) } // Show Button to simulate incoming messages self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage.jsq_defaultTypingIndicator(), style: .plain, target: self, action: #selector(receiveMessagePressed)) // This is a beta feature that mostly works but to make things more stable it is diabled. collectionView?.collectionViewLayout.springinessEnabled = false automaticallyScrollsToMostRecentMessage = true self.collectionView?.reloadData() self.collectionView?.layoutIfNeeded() } func setupBackButton() { let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(backButtonTapped)) navigationItem.leftBarButtonItem = backButton } func backButtonTapped() { dismiss(animated: true, completion: nil) } func receiveMessagePressed(_ sender: UIBarButtonItem) { /** * DEMO ONLY * * The following is simply to simulate received messages for the demo. * Do not actually do this. */ /** * Show the typing indicator to be shown */ self.showTypingIndicator = !self.showTypingIndicator /** * Scroll to actually view the indicator */ self.scrollToBottom(animated: true) /** * Copy last sent message, this will be the new "received" message */ var copyMessage = self.messages.last?.copy() if (copyMessage == nil) { copyMessage = JSQMessage(senderId: AvatarIdJobs, displayName: getName(User.Jobs), text: "First received!") } var newMessage:JSQMessage! var newMediaData:JSQMessageMediaData! var newMediaAttachmentCopy:AnyObject? if (copyMessage! as AnyObject).isMediaMessage() { /** * Last message was a media message */ let copyMediaData = (copyMessage! as AnyObject).media switch copyMediaData { case is JSQPhotoMediaItem: let photoItemCopy = (copyMediaData as! JSQPhotoMediaItem).copy() as! JSQPhotoMediaItem photoItemCopy.appliesMediaViewMaskAsOutgoing = false newMediaAttachmentCopy = UIImage(cgImage: photoItemCopy.image!.cgImage!) /** * Set image to nil to simulate "downloading" the image * and show the placeholder view5017 */ photoItemCopy.image = nil; newMediaData = photoItemCopy case is JSQLocationMediaItem: let locationItemCopy = (copyMediaData as! JSQLocationMediaItem).copy() as! JSQLocationMediaItem locationItemCopy.appliesMediaViewMaskAsOutgoing = false newMediaAttachmentCopy = locationItemCopy.location!.copy() as AnyObject? /** * Set location to nil to simulate "downloading" the location data */ locationItemCopy.location = nil; newMediaData = locationItemCopy; case is JSQVideoMediaItem: let videoItemCopy = (copyMediaData as! JSQVideoMediaItem).copy() as! JSQVideoMediaItem videoItemCopy.appliesMediaViewMaskAsOutgoing = false newMediaAttachmentCopy = (videoItemCopy.fileURL! as NSURL).copy() as AnyObject? /** * Reset video item to simulate "downloading" the video */ videoItemCopy.fileURL = nil; videoItemCopy.isReadyToPlay = false; newMediaData = videoItemCopy; case is JSQAudioMediaItem: let audioItemCopy = (copyMediaData as! JSQAudioMediaItem).copy() as! JSQAudioMediaItem audioItemCopy.appliesMediaViewMaskAsOutgoing = false newMediaAttachmentCopy = (audioItemCopy.audioData! as NSData).copy() as AnyObject? /** * Reset audio item to simulate "downloading" the audio */ audioItemCopy.audioData = nil; newMediaData = audioItemCopy; default: assertionFailure("Error: This Media type was not recognised") } newMessage = JSQMessage(senderId: AvatarIdJobs, displayName: getName(User.Jobs), media: newMediaData) } else { /** * Last message was a text message */ newMessage = JSQMessage(senderId: AvatarIdJobs, displayName: getName(User.Jobs), text: (copyMessage! as AnyObject).text) } /** * Upon receiving a message, you should: * * 1. Play sound (optional) * 2. Add new JSQMessageData object to your data source * 3. Call `finishReceivingMessage` */ self.messages.append(newMessage) self.finishReceivingMessage(animated: true) if newMessage.isMediaMessage { /** * Simulate "downloading" media */ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { /** * Media is "finished downloading", re-display visible cells * * If media cell is not visible, the next time it is dequeued the view controller will display its new attachment data * * Reload the specific item, or simply call `reloadData` */ switch newMediaData { case is JSQPhotoMediaItem: (newMediaData as! JSQPhotoMediaItem).image = newMediaAttachmentCopy as? UIImage self.collectionView!.reloadData() case is JSQLocationMediaItem: (newMediaData as! JSQLocationMediaItem).setLocation(newMediaAttachmentCopy as? CLLocation, withCompletionHandler: { self.collectionView!.reloadData() }) case is JSQVideoMediaItem: (newMediaData as! JSQVideoMediaItem).fileURL = newMediaAttachmentCopy as? URL (newMediaData as! JSQVideoMediaItem).isReadyToPlay = true self.collectionView!.reloadData() case is JSQAudioMediaItem: (newMediaData as! JSQAudioMediaItem).audioData = newMediaAttachmentCopy as? Data self.collectionView!.reloadData() default: assertionFailure("Error: This Media type was not recognised") } } } } // MARK: JSQMessagesViewController method overrides override func didPressSend(_ button: UIButton, withMessageText text: String, senderId: String, senderDisplayName: String, date: Date) { /** * Sending a message. Your implementation of this method should do *at least* the following: * * 1. Play sound (optional) * 2. Add new id object to your data source * 3. Call `finishSendingMessage` */ let message = JSQMessage(senderId: senderId, senderDisplayName: senderDisplayName, date: date, text: text) self.messages.append(message) self.finishSendingMessage(animated: true) } override func didPressAccessoryButton(_ sender: UIButton) { self.inputToolbar.contentView!.textView!.resignFirstResponder() let sheet = UIAlertController(title: "Media messages", message: nil, preferredStyle: .actionSheet) let photoAction = UIAlertAction(title: "Send photo", style: .default) { (action) in /** * Create fake photo */ let photoItem = JSQPhotoMediaItem(image: UIImage(named: "goldengate")) self.addMedia(photoItem) } let locationAction = UIAlertAction(title: "Send location", style: .default) { (action) in /** * Add fake location */ let locationItem = self.buildLocationItem() self.addMedia(locationItem) } let videoAction = UIAlertAction(title: "Send video", style: .default) { (action) in /** * Add fake video */ let videoItem = self.buildVideoItem() self.addMedia(videoItem) } let audioAction = UIAlertAction(title: "Send audio", style: .default) { (action) in /** * Add fake audio */ let audioItem = self.buildAudioItem() self.addMedia(audioItem) } let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) sheet.addAction(photoAction) sheet.addAction(locationAction) sheet.addAction(videoAction) sheet.addAction(audioAction) sheet.addAction(cancelAction) self.present(sheet, animated: true, completion: nil) } func buildVideoItem() -> JSQVideoMediaItem { let videoURL = URL(fileURLWithPath: "file://") let videoItem = JSQVideoMediaItem(fileURL: videoURL, isReadyToPlay: true) return videoItem } func buildAudioItem() -> JSQAudioMediaItem { let sample = Bundle.main.path(forResource: "jsq_messages_sample", ofType: "m4a") let audioData = try? Data(contentsOf: URL(fileURLWithPath: sample!)) let audioItem = JSQAudioMediaItem(data: audioData) return audioItem } func buildLocationItem() -> JSQLocationMediaItem { let ferryBuildingInSF = CLLocation(latitude: 37.795313, longitude: -122.393757) let locationItem = JSQLocationMediaItem() locationItem.setLocation(ferryBuildingInSF) { self.collectionView!.reloadData() } return locationItem } func addMedia(_ media:JSQMediaItem) { let message = JSQMessage(senderId: self.senderId(), displayName: self.senderDisplayName(), media: media) self.messages.append(message) //Optional: play sent sound self.finishSendingMessage(animated: true) } //MARK: JSQMessages CollectionView DataSource override func senderId() -> String { return User.Wozniak.rawValue } override func senderDisplayName() -> String { return getName(.Wozniak) } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return messages.count } override func collectionView(_ collectionView: JSQMessagesCollectionView, messageDataForItemAt indexPath: IndexPath) -> JSQMessageData { return messages[indexPath.item] } override func collectionView(_ collectionView: JSQMessagesCollectionView, messageBubbleImageDataForItemAt indexPath: IndexPath) -> JSQMessageBubbleImageDataSource { return messages[indexPath.item].senderId == self.senderId() ? outgoingBubble : incomingBubble } override func collectionView(_ collectionView: JSQMessagesCollectionView, avatarImageDataForItemAt indexPath: IndexPath) -> JSQMessageAvatarImageDataSource? { let message = messages[indexPath.item] return getAvatar(message.senderId) } override func collectionView(_ collectionView: JSQMessagesCollectionView, attributedTextForCellTopLabelAt indexPath: IndexPath) -> NSAttributedString? { /** * This logic should be consistent with what you return from `heightForCellTopLabelAtIndexPath:` * The other label text delegate methods should follow a similar pattern. * * Show a timestamp for every 3rd message */ if (indexPath.item % 3 == 0) { let message = self.messages[indexPath.item] return JSQMessagesTimestampFormatter.shared().attributedTimestamp(for: message.date) } return nil } override func collectionView(_ collectionView: JSQMessagesCollectionView, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath) -> NSAttributedString? { let message = messages[indexPath.item] // Displaying names above messages //Mark: Removing Sender Display Name /** * Example on showing or removing senderDisplayName based on user settings. * This logic should be consistent with what you return from `heightForCellTopLabelAtIndexPath:` */ if defaults.bool(forKey: Setting.removeSenderDisplayName.rawValue) { return nil } if message.senderId == self.senderId() { return nil } return NSAttributedString(string: message.senderDisplayName) } override func collectionView(_ collectionView: JSQMessagesCollectionView, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout, heightForCellTopLabelAt indexPath: IndexPath) -> CGFloat { /** * Each label in a cell has a `height` delegate method that corresponds to its text dataSource method */ /** * This logic should be consistent with what you return from `attributedTextForCellTopLabelAtIndexPath:` * The other label height delegate methods should follow similarly * * Show a timestamp for every 3rd message */ if indexPath.item % 3 == 0 { return kJSQMessagesCollectionViewCellLabelHeightDefault } return 0.0 } override func collectionView(_ collectionView: JSQMessagesCollectionView, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout, heightForMessageBubbleTopLabelAt indexPath: IndexPath) -> CGFloat { /** * Example on showing or removing senderDisplayName based on user settings. * This logic should be consistent with what you return from `attributedTextForCellTopLabelAtIndexPath:` */ if defaults.bool(forKey: Setting.removeSenderDisplayName.rawValue) { return 0.0 } /** * iOS7-style sender name labels */ let currentMessage = self.messages[indexPath.item] if currentMessage.senderId == self.senderId() { return 0.0 } if indexPath.item - 1 > 0 { let previousMessage = self.messages[indexPath.item - 1] if previousMessage.senderId == currentMessage.senderId { return 0.0 } } return kJSQMessagesCollectionViewCellLabelHeightDefault; } } ================================================ FILE: SwiftExample/SwiftExample/Conversation.swift ================================================ // // Conversation.swift // SwiftExample // // Created by Dan Leonard on 5/11/16. // Copyright © 2016 MacMeDan. All rights reserved. // import Foundation struct Conversation { let firstName: String? let lastName: String? let preferredName: String? let smsNumber: String let id: String? let latestMessage: String? let isRead: Bool } ================================================ FILE: SwiftExample/SwiftExample/DemoConversation.swift ================================================ // // DemoConversation.swift // SwiftExample // // Created by Dan Leonard on 5/11/16. // Copyright © 2016 MacMeDan. All rights reserved. // import JSQMessagesViewController // User Enum to make it easyier to work with. enum User: String { case Leonard = "053496-4509-288" case Squires = "053496-4509-289" case Jobs = "707-8956784-57" case Cook = "468-768355-23123" case Wozniak = "309-41802-93823" } // Helper Function to get usernames for a secific User. func getName(_ user: User) -> String{ switch user { case .Squires: return "Jesse Squires" case .Cook: return "Tim Cook" case .Wozniak: return "Steve Wozniak" case .Leonard: return "Dan Leonard" case .Jobs: return "Steve Jobs" } } //// Create Names to display //let DisplayNameSquires = "Jesse Squires" //let DisplayNameLeonard = "Dan Leonard" //let DisplayNameCook = "Tim Cook" //let DisplayNameJobs = "Steve Jobs" //let DisplayNameWoz = "Steve Wazniak" // Create Unique IDs for avatars let AvatarIDLeonard = "053496-4509-288" let AvatarIDSquires = "053496-4509-289" let AvatarIdCook = "468-768355-23123" let AvatarIdJobs = "707-8956784-57" let AvatarIdWoz = "309-41802-93823" // Create Avatars Once for performance // // Create an avatar with Image let AvatarLeonard = JSQMessagesAvatarImageFactory().avatarImage(withUserInitials: "DL", backgroundColor: UIColor.jsq_messageBubbleGreen(), textColor: UIColor.white, font: UIFont.systemFont(ofSize: 12)) let AvatarCook = JSQMessagesAvatarImageFactory().avatarImage(withUserInitials: "TC", backgroundColor: UIColor.gray, textColor: UIColor.white, font: UIFont.systemFont(ofSize: 12)) // Create avatar with Placeholder Image let AvatarJobs = JSQMessagesAvatarImageFactory().avatarImage(withPlaceholder: UIImage(named:"demo_avatar_jobs")!) let AvatarWoz = JSQMessagesAvatarImageFactory().avatarImage(withUserInitials: "SW", backgroundColor: UIColor.jsq_messageBubbleGreen(), textColor: UIColor.white, font: UIFont.systemFont(ofSize: 12)) let AvatarSquires = JSQMessagesAvatarImageFactory().avatarImage(withUserInitials: "JSQ", backgroundColor: UIColor.gray, textColor: UIColor.white, font: UIFont.systemFont(ofSize: 12)) // Helper Method for getting an avatar for a specific User. func getAvatar(_ id: String) -> JSQMessagesAvatarImage{ let user = User(rawValue: id)! switch user { case .Leonard: return AvatarLeonard case .Squires: return AvatarSquires case .Cook: return AvatarCook case .Wozniak: return AvatarWoz case .Jobs: return AvatarJobs } } // INFO: Creating Static Demo Data. This is only for the exsample project to show the framework at work. var conversationsList = [Conversation]() var convo = Conversation(firstName: "Steave", lastName: "Jobs", preferredName: "Stevie", smsNumber: "(987)987-9879", id: "33", latestMessage: "Holy Guacamole, JSQ in swift", isRead: false) var conversation = [JSQMessage]() let message = JSQMessage(senderId: AvatarIdCook, displayName: getName(User.Cook), text: "What is this Black Majic?") let message2 = JSQMessage(senderId: AvatarIDSquires, displayName: getName(User.Squires), text: "It is simple, elegant, and easy to use. There are super sweet default settings, but you can customize like crazy") let message3 = JSQMessage(senderId: AvatarIdWoz, displayName: getName(User.Wozniak), text: "It even has data detectors. You can call me tonight. My cell number is 123-456-7890. My website is www.hexedbits.com.") let message4 = JSQMessage(senderId: AvatarIdJobs, displayName: getName(User.Jobs), text: "JSQMessagesViewController is nearly an exact replica of the iOS Messages App. And perhaps, better.") let message5 = JSQMessage(senderId: AvatarIDLeonard, displayName: getName(User.Leonard), text: "It is unit-tested, free, open-source, and documented.") let message6 = JSQMessage(senderId: AvatarIDLeonard, displayName: getName(User.Leonard), text: "This is incredible") let message7 = JSQMessage(senderId: AvatarIdWoz, displayName: getName(User.Wozniak), text: "I would have to agree") let message8 = JSQMessage(senderId: AvatarIDLeonard, displayName: getName(User.Leonard), text: "It is unit-tested, free, open-source, and documented like a boss.") let message9 = JSQMessage(senderId: AvatarIdWoz, displayName: getName(User.Wozniak), text: "You guys need an award for this, I'll talk to my people at Apple. 💯 💯 💯") // photo message let photoItem = JSQPhotoMediaItem(image: UIImage(named: "goldengate")) let photoMessage = JSQMessage(senderId: AvatarIdWoz, displayName: getName(User.Wozniak), media: photoItem) // audio mesage let sample = Bundle.main.path(forResource: "jsq_messages_sample", ofType: "m4a") let audioData = try? Data(contentsOf: URL(fileURLWithPath: sample!)) let audioItem = JSQAudioMediaItem(data: audioData) let audioMessage = JSQMessage(senderId: AvatarIdWoz, displayName: getName(User.Wozniak), media: audioItem) func makeGroupConversation()->[JSQMessage] { conversation = [message, message2,message3, message4, message5, photoMessage, audioMessage] return conversation } func makeNormalConversation() -> [JSQMessage] { conversation = [message6, message7, message8, message9, photoMessage, audioMessage] return conversation } ================================================ FILE: SwiftExample/SwiftExample/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: SwiftExample/SwiftExample/InitalTableViewController.swift ================================================ // // InitalTableViewController.swift // SwiftExample // // Created by P D Leonard on 7/22/16. // Copyright © 2016 MacMeDan. All rights reserved. // import UIKit let cellIdentifier = "cellIdentifier" class InitalTableViewController: UITableViewController { //MARK: - View lifecycle override func viewDidLoad() { super.viewDidLoad() self.title = "JSQMessagesViewControler in Swift" tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier) } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 2 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch (section) { case 0: return 2 case 1: return 1 default: return 0 } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) else { return UITableViewCell() } switch indexPath.section { case 0: switch indexPath.row { case 0: cell.textLabel?.text = "Conversation between two people" break case 1: cell.textLabel?.text = "Group Conversation" break default: break } case 1: switch indexPath.row { case 0: cell.textLabel?.text = "Settings" break default: break } default: break } return cell } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { switch section { case 0: return "Examples" case 1: return "Options" default: return nil } } override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { switch section { case 0: return "Copyright © 2015\nJesse Squires\nMIT License" case 1: return "Thanks to all the contributers and MacMeDan for this swift example." default: return nil } } //Mark: - Table view delegate override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch indexPath.section { case 0: switch indexPath.row { case 0: let chatView = ChatViewController() chatView.messages = makeNormalConversation() let chatNavigationController = UINavigationController(rootViewController: chatView) present(chatNavigationController, animated: true, completion: nil) case 1: let chatView = ChatViewController() chatView.messages = makeGroupConversation() let chatNavigationController = UINavigationController(rootViewController: chatView) present(chatNavigationController, animated: true, completion: nil) default: return } case 1: switch indexPath.row { case 0: self.present(UINavigationController(rootViewController: SettingsTableViewController()), animated: true, completion: nil) default: return } default: return } } } ================================================ FILE: SwiftExample/SwiftExample/SettingsTableViewController.swift ================================================ // // SettingsTableViewController.swift // SwiftExample // // Created by Dan Leonard on 5/15/16. // Copyright © 2016 MacMeDan. All rights reserved. // import UIKit let cellReuseIdentifier = "settingsCell" public enum Setting: String{ case removeBubbleTails = "Remove message bubble tails" case removeSenderDisplayName = "Remove sender Display Name" case removeAvatar = "Remove Avatars" } let defaults = UserDefaults.standard var rows = [Setting]() class SettingsTableViewController: UITableViewController { //MARK: - View lifecycle override func viewDidLoad() { super.viewDidLoad() setupBackButton() rows = [.removeAvatar, .removeBubbleTails, .removeSenderDisplayName] // Set the Switch to the currents settings self.title = "Settings" tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier) } // MARK: - Table view data source override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) else { return UITableViewCell() } let row = rows[indexPath.row] let settingSwitch = UISwitch() settingSwitch.tag = indexPath.row settingSwitch.isOn = defaults.bool(forKey: row.rawValue) settingSwitch.addTarget(self, action: #selector(switchValueChanged), for: .valueChanged) cell.accessoryView = settingSwitch cell.textLabel?.text = row.rawValue return cell } func switchValueChanged(_ sender: UISwitch) { defaults.set(sender.isOn, forKey: rows[sender.tag].rawValue) } func setupBackButton() { let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(backButtonTapped)) navigationItem.leftBarButtonItem = backButton } func backButtonTapped() { dismiss(animated: true, completion: nil) } //Mark: - Table view delegate override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 3 } } ================================================ FILE: SwiftExample/SwiftExample.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 2D4E760C6D52AC095DEF0A67 /* Pods_SwiftExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A0DC4B7792ACC2835F9E2C4 /* Pods_SwiftExample.framework */; }; 371B88351D42DE7000CC7271 /* InitalTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B88341D42DE7000CC7271 /* InitalTableViewController.swift */; }; 378FC9851D43F3CD00DF0026 /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378FC9841D43F3CD00DF0026 /* SettingsTests.swift */; }; 41AF84C51CFCA73B006ED473 /* ChatViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41AF84C41CFCA73B006ED473 /* ChatViewControllerTests.swift */; }; 41AF84E51CFCFE17006ED473 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41AF84E41CFCFE17006ED473 /* Images.xcassets */; }; 872F10EC240969CC9051FF5B /* Pods_SwiftExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4403B1A26FE3CFF614F3F725 /* Pods_SwiftExampleTests.framework */; }; A0844FA11D38367C00D0EB83 /* jsq_messages_sample.m4a in Resources */ = {isa = PBXBuildFile; fileRef = A0844FA01D38367C00D0EB83 /* jsq_messages_sample.m4a */; }; F80276D11CE38FA600063D88 /* DemoConversation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80276D01CE38FA600063D88 /* DemoConversation.swift */; }; F80276D31CE38FEB00063D88 /* Conversation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80276D21CE38FEB00063D88 /* Conversation.swift */; }; F80276D51CE3915700063D88 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80276D41CE3915700063D88 /* ChatViewController.swift */; }; F82D09C31CDFBB4900DD74CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F82D09C21CDFBB4900DD74CF /* AppDelegate.swift */; }; F82D09C81CDFBB4900DD74CF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F82D09C61CDFBB4900DD74CF /* Main.storyboard */; }; F82D09CD1CDFBB4900DD74CF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F82D09CB1CDFBB4900DD74CF /* LaunchScreen.storyboard */; }; F82E19211CF0F2BE0069B211 /* SettingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F82E19201CF0F2BE0069B211 /* SettingsTableViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F848952E1CE0EE8800F5B654 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F82D09B71CDFBB4900DD74CF /* Project object */; proxyType = 1; remoteGlobalIDString = F82D09BE1CDFBB4900DD74CF; remoteInfo = SwiftExample; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 2A0DC4B7792ACC2835F9E2C4 /* Pods_SwiftExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 371B88341D42DE7000CC7271 /* InitalTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitalTableViewController.swift; sourceTree = ""; }; 378FC9841D43F3CD00DF0026 /* SettingsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = ""; }; 3D5DE46B5737B20C9B95257C /* Pods-SwiftExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.debug.xcconfig"; sourceTree = ""; }; 41AF84C41CFCA73B006ED473 /* ChatViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewControllerTests.swift; sourceTree = ""; }; 41AF84E41CFCFE17006ED473 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ../../JSQMessagesDemo/Images.xcassets; sourceTree = ""; }; 4403B1A26FE3CFF614F3F725 /* Pods_SwiftExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6C0AE5EA1B73CE4C012F9057 /* Pods-SwiftExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftExampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.debug.xcconfig"; sourceTree = ""; }; 74B7033A0AA00050D9200C2B /* Pods-SwiftExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftExampleTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.release.xcconfig"; sourceTree = ""; }; 8F35C314AF75B616FAD01E7A /* Pods-SwiftExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.release.xcconfig"; sourceTree = ""; }; A0844FA01D38367C00D0EB83 /* jsq_messages_sample.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; name = jsq_messages_sample.m4a; path = ../../JSQMessagesDemo/jsq_messages_sample.m4a; sourceTree = ""; }; F80276D01CE38FA600063D88 /* DemoConversation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoConversation.swift; sourceTree = ""; }; F80276D21CE38FEB00063D88 /* Conversation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Conversation.swift; sourceTree = ""; }; F80276D41CE3915700063D88 /* ChatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = ""; }; F82D09BF1CDFBB4900DD74CF /* SwiftExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; F82D09C21CDFBB4900DD74CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F82D09C71CDFBB4900DD74CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; F82D09CC1CDFBB4900DD74CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; F82D09CE1CDFBB4900DD74CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F82E19201CF0F2BE0069B211 /* SettingsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewController.swift; sourceTree = ""; }; F84895291CE0EE8800F5B654 /* SwiftExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F848952D1CE0EE8800F5B654 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F82D09BC1CDFBB4900DD74CF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2D4E760C6D52AC095DEF0A67 /* Pods_SwiftExample.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F84895261CE0EE8800F5B654 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 872F10EC240969CC9051FF5B /* Pods_SwiftExampleTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 21D0F5FC767AD74A06627E72 /* Pods */ = { isa = PBXGroup; children = ( 3D5DE46B5737B20C9B95257C /* Pods-SwiftExample.debug.xcconfig */, 8F35C314AF75B616FAD01E7A /* Pods-SwiftExample.release.xcconfig */, 6C0AE5EA1B73CE4C012F9057 /* Pods-SwiftExampleTests.debug.xcconfig */, 74B7033A0AA00050D9200C2B /* Pods-SwiftExampleTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 93B54F50FF5261C4DD621D31 /* Frameworks */ = { isa = PBXGroup; children = ( 2A0DC4B7792ACC2835F9E2C4 /* Pods_SwiftExample.framework */, 4403B1A26FE3CFF614F3F725 /* Pods_SwiftExampleTests.framework */, ); name = Frameworks; sourceTree = ""; }; F82D09B61CDFBB4900DD74CF = { isa = PBXGroup; children = ( F82D09C11CDFBB4900DD74CF /* SwiftExample */, F848952A1CE0EE8800F5B654 /* SwiftExampleTests */, F82D09C01CDFBB4900DD74CF /* Products */, 21D0F5FC767AD74A06627E72 /* Pods */, 93B54F50FF5261C4DD621D31 /* Frameworks */, ); sourceTree = ""; }; F82D09C01CDFBB4900DD74CF /* Products */ = { isa = PBXGroup; children = ( F82D09BF1CDFBB4900DD74CF /* SwiftExample.app */, F84895291CE0EE8800F5B654 /* SwiftExampleTests.xctest */, ); name = Products; sourceTree = ""; }; F82D09C11CDFBB4900DD74CF /* SwiftExample */ = { isa = PBXGroup; children = ( F82D09C21CDFBB4900DD74CF /* AppDelegate.swift */, F80276D01CE38FA600063D88 /* DemoConversation.swift */, F80276D41CE3915700063D88 /* ChatViewController.swift */, F80276D21CE38FEB00063D88 /* Conversation.swift */, 371B88341D42DE7000CC7271 /* InitalTableViewController.swift */, F82E19201CF0F2BE0069B211 /* SettingsTableViewController.swift */, F82D09C61CDFBB4900DD74CF /* Main.storyboard */, 41AF84E41CFCFE17006ED473 /* Images.xcassets */, F82D09CB1CDFBB4900DD74CF /* LaunchScreen.storyboard */, A0844FA01D38367C00D0EB83 /* jsq_messages_sample.m4a */, F82D09CE1CDFBB4900DD74CF /* Info.plist */, ); path = SwiftExample; sourceTree = ""; }; F848952A1CE0EE8800F5B654 /* SwiftExampleTests */ = { isa = PBXGroup; children = ( F848952D1CE0EE8800F5B654 /* Info.plist */, 41AF84C41CFCA73B006ED473 /* ChatViewControllerTests.swift */, 378FC9841D43F3CD00DF0026 /* SettingsTests.swift */, ); path = SwiftExampleTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F82D09BE1CDFBB4900DD74CF /* SwiftExample */ = { isa = PBXNativeTarget; buildConfigurationList = F82D09D11CDFBB4900DD74CF /* Build configuration list for PBXNativeTarget "SwiftExample" */; buildPhases = ( 6F1D881EB82C4A076B2FDD24 /* [CP] Check Pods Manifest.lock */, F82D09BB1CDFBB4900DD74CF /* Sources */, F82D09BC1CDFBB4900DD74CF /* Frameworks */, F82D09BD1CDFBB4900DD74CF /* Resources */, F2DFA70BCADAEEAED3C8CE1A /* [CP] Embed Pods Frameworks */, 2243A79665CACC4CBD6D7DFF /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = SwiftExample; productName = SwiftExample; productReference = F82D09BF1CDFBB4900DD74CF /* SwiftExample.app */; productType = "com.apple.product-type.application"; }; F84895281CE0EE8800F5B654 /* SwiftExampleTests */ = { isa = PBXNativeTarget; buildConfigurationList = F84895321CE0EE8800F5B654 /* Build configuration list for PBXNativeTarget "SwiftExampleTests" */; buildPhases = ( 87263242F5AF11CD38D2F990 /* [CP] Check Pods Manifest.lock */, F84895251CE0EE8800F5B654 /* Sources */, F84895261CE0EE8800F5B654 /* Frameworks */, F84895271CE0EE8800F5B654 /* Resources */, 0490B7E6F4ACA033A3B3434E /* [CP] Embed Pods Frameworks */, DE1DEABBB7B89A8EE25C6039 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( F848952F1CE0EE8800F5B654 /* PBXTargetDependency */, ); name = SwiftExampleTests; productName = SwiftExampleTests; productReference = F84895291CE0EE8800F5B654 /* SwiftExampleTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F82D09B71CDFBB4900DD74CF /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0800; ORGANIZATIONNAME = MacMeDan; TargetAttributes = { F82D09BE1CDFBB4900DD74CF = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0800; }; F84895281CE0EE8800F5B654 = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0800; TestTargetID = F82D09BE1CDFBB4900DD74CF; }; }; }; buildConfigurationList = F82D09BA1CDFBB4900DD74CF /* Build configuration list for PBXProject "SwiftExample" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = F82D09B61CDFBB4900DD74CF; productRefGroup = F82D09C01CDFBB4900DD74CF /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F82D09BE1CDFBB4900DD74CF /* SwiftExample */, F84895281CE0EE8800F5B654 /* SwiftExampleTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ F82D09BD1CDFBB4900DD74CF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( F82D09CD1CDFBB4900DD74CF /* LaunchScreen.storyboard in Resources */, A0844FA11D38367C00D0EB83 /* jsq_messages_sample.m4a in Resources */, 41AF84E51CFCFE17006ED473 /* Images.xcassets in Resources */, F82D09C81CDFBB4900DD74CF /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F84895271CE0EE8800F5B654 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 0490B7E6F4ACA033A3B3434E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 2243A79665CACC4CBD6D7DFF /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-resources.sh\"\n"; showEnvVarsInLog = 0; }; 6F1D881EB82C4A076B2FDD24 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 87263242F5AF11CD38D2F990 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; DE1DEABBB7B89A8EE25C6039 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; F2DFA70BCADAEEAED3C8CE1A /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ F82D09BB1CDFBB4900DD74CF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F80276D31CE38FEB00063D88 /* Conversation.swift in Sources */, F80276D11CE38FA600063D88 /* DemoConversation.swift in Sources */, F80276D51CE3915700063D88 /* ChatViewController.swift in Sources */, F82D09C31CDFBB4900DD74CF /* AppDelegate.swift in Sources */, F82E19211CF0F2BE0069B211 /* SettingsTableViewController.swift in Sources */, 371B88351D42DE7000CC7271 /* InitalTableViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F84895251CE0EE8800F5B654 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 378FC9851D43F3CD00DF0026 /* SettingsTests.swift in Sources */, 41AF84C51CFCA73B006ED473 /* ChatViewControllerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F848952F1CE0EE8800F5B654 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F82D09BE1CDFBB4900DD74CF /* SwiftExample */; targetProxy = F848952E1CE0EE8800F5B654 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ F82D09C61CDFBB4900DD74CF /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( F82D09C71CDFBB4900DD74CF /* Base */, ); name = Main.storyboard; sourceTree = ""; }; F82D09CB1CDFBB4900DD74CF /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( F82D09CC1CDFBB4900DD74CF /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ F82D09CF1CDFBB4900DD74CF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; F82D09D01CDFBB4900DD74CF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; F82D09D21CDFBB4900DD74CF /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3D5DE46B5737B20C9B95257C /* Pods-SwiftExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = SwiftExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.MacMeDan.SwiftExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; name = Debug; }; F82D09D31CDFBB4900DD74CF /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8F35C314AF75B616FAD01E7A /* Pods-SwiftExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = SwiftExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.MacMeDan.SwiftExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; name = Release; }; F84895301CE0EE8800F5B654 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 6C0AE5EA1B73CE4C012F9057 /* Pods-SwiftExampleTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = SwiftExampleTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.MacMeDan.SwiftExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftExample.app/SwiftExample"; }; name = Debug; }; F84895311CE0EE8800F5B654 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 74B7033A0AA00050D9200C2B /* Pods-SwiftExampleTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = SwiftExampleTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.MacMeDan.SwiftExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftExample.app/SwiftExample"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F82D09BA1CDFBB4900DD74CF /* Build configuration list for PBXProject "SwiftExample" */ = { isa = XCConfigurationList; buildConfigurations = ( F82D09CF1CDFBB4900DD74CF /* Debug */, F82D09D01CDFBB4900DD74CF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F82D09D11CDFBB4900DD74CF /* Build configuration list for PBXNativeTarget "SwiftExample" */ = { isa = XCConfigurationList; buildConfigurations = ( F82D09D21CDFBB4900DD74CF /* Debug */, F82D09D31CDFBB4900DD74CF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F84895321CE0EE8800F5B654 /* Build configuration list for PBXNativeTarget "SwiftExampleTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F84895301CE0EE8800F5B654 /* Debug */, F84895311CE0EE8800F5B654 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F82D09B71CDFBB4900DD74CF /* Project object */; } ================================================ FILE: SwiftExample/SwiftExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SwiftExample/SwiftExample.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SwiftExample/SwiftExampleTests/ChatViewControllerTests.swift ================================================ // // ViewControllertTests.swift // SwiftExample // // Created by Gianni Carlo on 5/30/16. // Copyright © 2016 MacMeDan. All rights reserved. // import UIKit import XCTest import JSQMessagesViewController @testable import SwiftExample class ChatViewControllerTests: XCTestCase { let defaults = UserDefaults.standard let chatViewController = ChatViewController() override func setUp() { super.setUp() let chatViewController = ChatViewController() chatViewController.messages = makeNormalConversation() // This ensures that ViewDidLoad() has been called let _ = chatViewController.view } override func tearDown() { super.tearDown() defaults.set(false, forKey: Setting.removeAvatar.rawValue) defaults.set(false, forKey: Setting.removeSenderDisplayName.rawValue) defaults.set(false, forKey: Setting.removeBubbleTails.rawValue) } func testSendButtonAction() { let _ = chatViewController.view let button = self.chatViewController.inputToolbar.sendButtonLocation == .right ? self.chatViewController.inputToolbar.contentView!.rightBarButtonItem! : self.chatViewController.inputToolbar.contentView!.leftBarButtonItem! let text = "Testing text" let senderId = self.chatViewController.senderId() let senderDisplayName = self.chatViewController.senderDisplayName() let date = Date() let originalCount = self.chatViewController.messages.count self.chatViewController.didPressSend(button, withMessageText: text, senderId: senderId, senderDisplayName: senderDisplayName, date: date) let newCount = self.chatViewController.messages.count XCTAssert(newCount == (originalCount + 1)) let newMessage = self.chatViewController.messages.last! XCTAssert(newMessage.senderId == senderId) XCTAssert(newMessage.senderDisplayName == senderDisplayName) XCTAssert(newMessage.date == date) XCTAssert(newMessage.text == text) } func testSendImage() { let senderId = self.chatViewController.senderId() let senderDisplayName = self.chatViewController.senderDisplayName() let photoItem = JSQPhotoMediaItem(image: UIImage(named: "goldengate")) self.chatViewController.addMedia(photoItem) let newMessage = self.chatViewController.messages.last! XCTAssert(newMessage.senderId == senderId) XCTAssert(newMessage.senderDisplayName == senderDisplayName) XCTAssert(newMessage.media is JSQPhotoMediaItem) } func testSendLocation() { let senderId = self.chatViewController.senderId() let senderDisplayName = self.chatViewController.senderDisplayName() let locationItem = self.chatViewController.buildLocationItem() self.chatViewController.addMedia(locationItem) let newMessage = self.chatViewController.messages.last! XCTAssert(newMessage.senderId == senderId) XCTAssert(newMessage.senderDisplayName == senderDisplayName) XCTAssert(newMessage.media is JSQLocationMediaItem) } func testSendVideo() { let senderId = self.chatViewController.senderId() let senderDisplayName = self.chatViewController.senderDisplayName() let videoItem = self.chatViewController.buildVideoItem() self.chatViewController.addMedia(videoItem) let newMessage = self.chatViewController.messages.last! XCTAssert(newMessage.senderId == senderId) XCTAssert(newMessage.senderDisplayName == senderDisplayName) XCTAssert(newMessage.media is JSQVideoMediaItem) } func testSendAudio() { let senderId = self.chatViewController.senderId() let senderDisplayName = self.chatViewController.senderDisplayName() let audioItem = self.chatViewController.buildAudioItem() self.chatViewController.addMedia(audioItem) let newMessage = self.chatViewController.messages.last! XCTAssert(newMessage.senderId == senderId) XCTAssert(newMessage.senderDisplayName == senderDisplayName) XCTAssert(newMessage.media is JSQAudioMediaItem) } /** * Test when the messages array is empty, it should add a new incoming text message * Test when the messages array last message is a text message, it should add a new incoming text message */ func testSimulatedIncomingTextMessage() { self.chatViewController.messages = [] let _ = chatViewController.view self.chatViewController.collectionView!.reloadData() // trigger action let rightBarButton = self.chatViewController.navigationItem.rightBarButtonItem! rightBarButton.target!.perform(rightBarButton.action, with: rightBarButton) let lastMessage = self.chatViewController.messages.last! XCTAssert(!lastMessage.isMediaMessage) XCTAssert(lastMessage.senderId != self.chatViewController.senderId()) XCTAssert(lastMessage.senderDisplayName != self.chatViewController.senderDisplayName()) // triger action rightBarButton.target!.perform(rightBarButton.action, with: rightBarButton) let newMessage = self.chatViewController.messages.last! XCTAssert(newMessage != lastMessage) XCTAssert(!newMessage.isMediaMessage) XCTAssert(newMessage.senderId != self.chatViewController.senderId()) XCTAssert(newMessage.senderDisplayName != self.chatViewController.senderDisplayName()) } /** * Simulate that last message is an image and test message received functionality */ func testSimulatedIncomingImage() { // add image let photoItem = JSQPhotoMediaItem(image: UIImage(named: "goldengate")) self.chatViewController.addMedia(photoItem) let _ = chatViewController.view let lastMessage = self.chatViewController.messages.last! // trigger action let rightBarButton = self.chatViewController.navigationItem.rightBarButtonItem! rightBarButton.target!.perform(rightBarButton.action, with: rightBarButton) let newMessage = self.chatViewController.messages.last! XCTAssert(newMessage != lastMessage) XCTAssert(newMessage.media is JSQPhotoMediaItem) XCTAssert(newMessage.senderId != self.chatViewController.senderId()) XCTAssert(newMessage.senderDisplayName != self.chatViewController.senderDisplayName()) } /** * Simulate that last message is a location and test message received functionality */ func testSimulatedIncomingLocation() { // add location let locationItem = self.chatViewController.buildLocationItem() self.chatViewController.addMedia(locationItem) let _ = chatViewController.view let lastMessage = self.chatViewController.messages.last! // trigger action let rightBarButton = self.chatViewController.navigationItem.rightBarButtonItem! rightBarButton.target!.perform(rightBarButton.action, with: rightBarButton) let newMessage = self.chatViewController.messages.last! XCTAssert(newMessage != lastMessage) XCTAssert(newMessage.media is JSQLocationMediaItem) XCTAssert(newMessage.senderId != self.chatViewController.senderId()) XCTAssert(newMessage.senderDisplayName != self.chatViewController.senderDisplayName()) } func testRemoveAvatarSetting() { defaults.set(true, forKey: Setting.removeAvatar.rawValue) let _ = chatViewController.view XCTAssertEqual(chatViewController.collectionView?.collectionViewLayout.incomingAvatarViewSize, .zero, "Incoming Avatar should be hidden") XCTAssertEqual(chatViewController.collectionView?.collectionViewLayout.outgoingAvatarViewSize, .zero, "Outgoing Avatar should be hidden") } func testSenderDisplayNameDefaultSetting() { defaults.set(false, forKey: Setting.removeSenderDisplayName.rawValue) let _ = chatViewController.view let button = self.chatViewController.inputToolbar.sendButtonLocation == .right ? self.chatViewController.inputToolbar.contentView!.rightBarButtonItem! : self.chatViewController.inputToolbar.contentView!.leftBarButtonItem! let sender = User.Cook self.chatViewController.didPressSend(button, withMessageText: "Testing Text", senderId: sender.rawValue, senderDisplayName: getName(sender), date: Date()) let senderDisplayName = chatViewController.collectionView(self.chatViewController.collectionView!, attributedTextForMessageBubbleTopLabelAt: IndexPath(item: self.chatViewController.messages.count - 1, section: 0)) XCTAssertNotNil(senderDisplayName, "Sender Display should not be nil") } func testRemoveSenderDisplayNameSetting() { defaults.set(true, forKey: Setting.removeSenderDisplayName.rawValue) let _ = chatViewController.view let button = self.chatViewController.inputToolbar.sendButtonLocation == .right ? self.chatViewController.inputToolbar.contentView!.rightBarButtonItem! : self.chatViewController.inputToolbar.contentView!.leftBarButtonItem! self.chatViewController.didPressSend(button, withMessageText: "Testing Text", senderId: chatViewController.senderId(), senderDisplayName: chatViewController.senderDisplayName(), date: Date()) XCTAssertNil(chatViewController.collectionView(self.chatViewController.collectionView!, attributedTextForMessageBubbleTopLabelAt: IndexPath(item: self.chatViewController.messages.count - 1, section: 0)), "Sender Display should be nil") } } ================================================ FILE: SwiftExample/SwiftExampleTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: SwiftExample/SwiftExampleTests/SettingsTests.swift ================================================ // // SettingsTests.swift // SwiftExample // // Created by P D Leonard on 7/23/16. // Copyright © 2016 MacMeDan. All rights reserved. // import XCTest @testable import SwiftExample class SettingsTests: XCTestCase { let settingsView = SettingsTableViewController() override func setUp() { super.setUp() } func testSettingsView() { let _ = settingsView.view XCTAssertEqual(settingsView.tableView.numberOfRows(inSection: 0), 3) } }