diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..3d47e364f --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,33 @@ +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 diff --git a/.github/CONDUCT.md b/.github/CONDUCT.md new file mode 100644 index 000000000..bdd821059 --- /dev/null +++ b/.github/CONDUCT.md @@ -0,0 +1,74 @@ +# 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/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..415fd18df --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# 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: diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..5cda56555 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,20 @@ +> 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? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..a6c342bb9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +> 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? + diff --git a/.gitignore b/.gitignore index 9e5f0fe57..d0e4a3359 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -# Xcode - .DS_Store + +# Xcode /build/* */build/* *.pbxuser @@ -18,8 +18,3 @@ DerivedData .idea/ *.hmap *.xccheckout - - -#CocoaPods - -Pods \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 19639b682..fac0eebe8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,60 @@ language: objective-c +osx_image: xcode8 +cache: cocoapods -before_install: -- export LANG=en_US.UTF-8 -- gem install cocoapods -- brew update -- if brew outdated | grep -qx xctool; then brew upgrade xctool; fi +env: + global: + - LANG=en_US.UTF-8 -script: -- xctool clean build test -workspace JSQMessages.xcworkspace -scheme JSQMessages -sdk iphonesimulator8.1 ONLY_ACTIVE_ARCH=NO + - 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); diff --git a/Assets/JSQmessages.png b/Assets/JSQmessages.png index 844931424..5af74b08a 100644 Binary files a/Assets/JSQmessages.png and b/Assets/JSQmessages.png differ diff --git a/Assets/jsq_message_chat_icon.png b/Assets/jsq_message_chat_icon.png index 3ff20054c..30e7e443d 100644 Binary files a/Assets/jsq_message_chat_icon.png and b/Assets/jsq_message_chat_icon.png differ diff --git a/Assets/jsq_messages_banner.png b/Assets/jsq_messages_banner.png index cb6404c19..0bb8c11d1 100644 Binary files a/Assets/jsq_messages_banner.png and b/Assets/jsq_messages_banner.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..4cadcf61f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,154 @@ +# 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. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index f54bfbc56..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,3 +0,0 @@ -## How To Contribute - -Please follow these sweet [contribution guidelines](https://github.com/jessesquires/HowToContribute). diff --git a/Documentation/apps_using_this_library.md b/Documentation/apps_using_this_library.md new file mode 100644 index 000000000..2db9c0463 --- /dev/null +++ b/Documentation/apps_using_this_library.md @@ -0,0 +1,54 @@ +# 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)!* diff --git a/Documentation/contributor_onboarding.md b/Documentation/contributor_onboarding.md new file mode 100644 index 000000000..d72a91aad --- /dev/null +++ b/Documentation/contributor_onboarding.md @@ -0,0 +1,131 @@ +# 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. diff --git a/Documentation/faq.md b/Documentation/faq.md new file mode 100644 index 000000000..29798ce5f --- /dev/null +++ b/Documentation/faq.md @@ -0,0 +1,111 @@ +# 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; +} +```` diff --git a/Documentation/getting_started.md b/Documentation/getting_started.md new file mode 100644 index 000000000..df86a0cf9 --- /dev/null +++ b/Documentation/getting_started.md @@ -0,0 +1,56 @@ +# 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. diff --git a/Documentation/migration.md b/Documentation/migration.md new file mode 100644 index 000000000..23fb23203 --- /dev/null +++ b/Documentation/migration.md @@ -0,0 +1,17 @@ +# 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. diff --git a/JSQMessages.xcodeproj/project.pbxproj b/JSQMessages.xcodeproj/project.pbxproj index aa3608687..aa19806ad 100644 --- a/JSQMessages.xcodeproj/project.pbxproj +++ b/JSQMessages.xcodeproj/project.pbxproj @@ -7,8 +7,12 @@ objects = { /* Begin PBXBuildFile section */ - 36CF33BD29CF36EB06D0CCFD /* libPods-JSQMessagesTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 782026E9E518622532ED474D /* libPods-JSQMessagesTests.a */; }; - 77CC17A895E6E12BC9CB549A /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 97E6750B77E8A7042BA0754B /* libPods.a */; }; + 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 */; }; @@ -29,7 +33,6 @@ 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 */; }; - 886FFD2E19E9A65D00EB8485 /* UIDevice+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 886FFD2D19E9A65D00EB8485 /* UIDevice+JSQMessages.m */; }; 8873B60C1AB7B244006DF9AC /* NSBundle+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 8873B60B1AB7B244006DF9AC /* NSBundle+JSQMessages.m */; }; 8873B60E1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8873B60D1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m */; }; 8885734A19DE540400E89D20 /* DemoSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8885734919DE540400E89D20 /* DemoSettingsViewController.m */; }; @@ -38,12 +41,10 @@ 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 */; }; - 88A25FB519D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F5619D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.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 */; }; - 88A25FBA19D8E01A00924534 /* JSQMessagesKeyboardController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25F6119D8E01A00924534 /* JSQMessagesKeyboardController.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 */; }; @@ -79,7 +80,6 @@ 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 */; }; - 88A2600519D8E18400924534 /* JSQMessagesKeyboardControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A25FE819D8E18400924534 /* JSQMessagesKeyboardControllerTests.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 */; }; @@ -101,11 +101,13 @@ 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 */; }; - 94A4FA20C2FBD0D62614D5A8 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 97E6750B77E8A7042BA0754B /* libPods.a */; }; + 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 */ @@ -119,10 +121,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 0844AD596023C7658D39E241 /* Pods-JSQMessagesTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JSQMessagesTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-JSQMessagesTests/Pods-JSQMessagesTests.release.xcconfig"; sourceTree = ""; }; - 223FBACE0F24ADEF8B7F3F24 /* Pods-JSQMessagesTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JSQMessagesTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-JSQMessagesTests/Pods-JSQMessagesTests.debug.xcconfig"; sourceTree = ""; }; - 27B7FD1B722B36B26CB3460B /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; - 782026E9E518622532ED474D /* libPods-JSQMessagesTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JSQMessagesTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; @@ -141,8 +150,6 @@ 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 = ""; }; - 886FFD2C19E9A65D00EB8485 /* UIDevice+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIDevice+JSQMessages.h"; sourceTree = ""; }; - 886FFD2D19E9A65D00EB8485 /* UIDevice+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+JSQMessages.m"; 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 = ""; }; @@ -158,8 +165,6 @@ 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 = ""; }; - 88A25F5519D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQSystemSoundPlayer+JSQMessages.h"; sourceTree = ""; }; - 88A25F5619D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQSystemSoundPlayer+JSQMessages.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 = ""; }; @@ -168,8 +173,6 @@ 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 = ""; }; - 88A25F6019D8E01A00924534 /* JSQMessagesKeyboardController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesKeyboardController.h; sourceTree = ""; }; - 88A25F6119D8E01A00924534 /* JSQMessagesKeyboardController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesKeyboardController.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 = ""; }; @@ -237,7 +240,6 @@ 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 = ""; }; - 88A25FE819D8E18400924534 /* JSQMessagesKeyboardControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesKeyboardControllerTests.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 = ""; }; @@ -260,13 +262,18 @@ 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 = ""; }; - 97E6750B77E8A7042BA0754B /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; - FC5C727E4CCDA2B95A7BA30C /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; 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 */ @@ -274,13 +281,13 @@ 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 */, - 77CC17A895E6E12BC9CB549A /* libPods.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -295,33 +302,19 @@ 88445B3B19E0C0B10014F889 /* XCTest.framework in Frameworks */, 88445B3919E0C0AC0014F889 /* Foundation.framework in Frameworks */, 88445B3819E0C0A70014F889 /* UIKit.framework in Frameworks */, - 94A4FA20C2FBD0D62614D5A8 /* libPods.a in Frameworks */, - 36CF33BD29CF36EB06D0CCFD /* libPods-JSQMessagesTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 2BBEF3CD91C31A49E5FF9E3C /* Pods */ = { - isa = PBXGroup; - children = ( - FC5C727E4CCDA2B95A7BA30C /* Pods.debug.xcconfig */, - 27B7FD1B722B36B26CB3460B /* Pods.release.xcconfig */, - 223FBACE0F24ADEF8B7F3F24 /* Pods-JSQMessagesTests.debug.xcconfig */, - 0844AD596023C7658D39E241 /* Pods-JSQMessagesTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; 636A8663AEEE5C37B65C515D /* Frameworks */ = { isa = PBXGroup; children = ( + 1F0EFE0E1AC23D7E003FF3DB /* MobileCoreServices.framework */, 88445B3419E0AE4A0014F889 /* CoreGraphics.framework */, 88445B4119E1B50B0014F889 /* CoreLocation.framework */, 88445B3219E0AE450014F889 /* Foundation.framework */, - 782026E9E518622532ED474D /* libPods-JSQMessagesTests.a */, - 97E6750B77E8A7042BA0754B /* libPods.a */, 88445B4319E1B5110014F889 /* MapKit.framework */, 88445B3619E0AE5C0014F889 /* QuartzCore.framework */, 88445B3019E0AE3F0014F889 /* UIKit.framework */, @@ -338,7 +331,6 @@ 636A8663AEEE5C37B65C515D /* Frameworks */, 88A25F2B19D8DF2500924534 /* JSQMessagesDemo */, 88A25F1E19D8DEC500924534 /* JSQMessagesTests */, - 2BBEF3CD91C31A49E5FF9E3C /* Pods */, 88A25F0319D8DEC400924534 /* Products */, ); sourceTree = ""; @@ -387,12 +379,14 @@ 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 = ""; @@ -423,16 +417,12 @@ 88A25F5419D8E01A00924534 /* Categories */ = { isa = PBXGroup; children = ( - 88A25F5519D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.h */, - 88A25F5619D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.m */, 8873B60A1AB7B244006DF9AC /* NSBundle+JSQMessages.h */, 8873B60B1AB7B244006DF9AC /* NSBundle+JSQMessages.m */, 88A25F5719D8E01A00924534 /* NSString+JSQMessages.h */, 88A25F5819D8E01A00924534 /* NSString+JSQMessages.m */, 88A25F5919D8E01A00924534 /* UIColor+JSQMessages.h */, 88A25F5A19D8E01A00924534 /* UIColor+JSQMessages.m */, - 886FFD2C19E9A65D00EB8485 /* UIDevice+JSQMessages.h */, - 886FFD2D19E9A65D00EB8485 /* UIDevice+JSQMessages.m */, 88A25F5B19D8E01A00924534 /* UIImage+JSQMessages.h */, 88A25F5C19D8E01A00924534 /* UIImage+JSQMessages.m */, 88A25F5D19D8E01A00924534 /* UIView+JSQMessages.h */, @@ -444,8 +434,6 @@ 88A25F5F19D8E01A00924534 /* Controllers */ = { isa = PBXGroup; children = ( - 88A25F6019D8E01A00924534 /* JSQMessagesKeyboardController.h */, - 88A25F6119D8E01A00924534 /* JSQMessagesKeyboardController.m */, 88A25F6219D8E01A00924534 /* JSQMessagesViewController.h */, 88A25F6319D8E01A00924534 /* JSQMessagesViewController.m */, 88A25F6419D8E01A00924534 /* JSQMessagesViewController.xib */, @@ -466,6 +454,8 @@ 88A25F6B19D8E01A00924534 /* JSQMessagesTimestampFormatter.m */, 88A25F6C19D8E01A00924534 /* JSQMessagesToolbarButtonFactory.h */, 88A25F6D19D8E01A00924534 /* JSQMessagesToolbarButtonFactory.m */, + A04B0EBD1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.h */, + A04B0EBE1D6ADE5800FBDC47 /* JSQMessagesVideoThumbnailFactory.m */, ); path = Factories; sourceTree = ""; @@ -473,6 +463,11 @@ 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 */, @@ -486,6 +481,8 @@ 88A25F7619D8E01A00924534 /* Model */ = { isa = PBXGroup; children = ( + 54271E3C1C905B9200294290 /* JSQAudioMediaItem.h */, + 54271E3D1C905B9200294290 /* JSQAudioMediaItem.m */, 88445B3E19E1B4470014F889 /* JSQLocationMediaItem.h */, 88445B3F19E1B4470014F889 /* JSQLocationMediaItem.m */, 88A901B419F618B100F99777 /* JSQMediaItem.h */, @@ -506,6 +503,7 @@ 88A25F8619D8E01A00924534 /* JSQPhotoMediaItem.m */, 886C33FB19F4371E006B4997 /* JSQVideoMediaItem.h */, 886C33FC19F4371E006B4997 /* JSQVideoMediaItem.m */, + 58620BCC6ABA99E3C6FD36F5 /* JSQMessagesViewAccessoryButtonDelegate.h */, ); path = Model; sourceTree = ""; @@ -542,6 +540,8 @@ 88A25FA019D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.h */, 88A25FA119D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.m */, 88A25FA219D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.xib */, + BF10D6A81D062AD10072D215 /* JSQMessagesTypingView.h */, + BF10D6A91D062AD10072D215 /* JSQMessagesTypingView.m */, ); path = Views; sourceTree = ""; @@ -561,7 +561,6 @@ 88A25FE719D8E18400924534 /* ControllerTests */ = { isa = PBXGroup; children = ( - 88A25FE819D8E18400924534 /* JSQMessagesKeyboardControllerTests.m */, 88A25FE919D8E18400924534 /* JSQMessagesViewControllerTests.m */, ); path = ControllerTests; @@ -591,6 +590,7 @@ 88A25FF219D8E18400924534 /* ModelTests */ = { isa = PBXGroup; children = ( + 54271E3F1C905D1600294290 /* JSQAudioMediaItemTests.m */, 88C00A4D1A44D4C600B004B3 /* JSQLocationMediaItemTests.m */, 88A25FF319D8E18400924534 /* JSQMessageMediaTests.m */, 88A25FF419D8E18400924534 /* JSQMessagesAvatarImageTests.m */, @@ -624,11 +624,9 @@ isa = PBXNativeTarget; buildConfigurationList = 88A25F2519D8DEC500924534 /* Build configuration list for PBXNativeTarget "JSQMessages" */; buildPhases = ( - 3AF3068570D5C74873D84E30 /* Check Pods Manifest.lock */, 88A25EFE19D8DEC400924534 /* Sources */, 88A25EFF19D8DEC400924534 /* Frameworks */, 88A25F0019D8DEC400924534 /* Resources */, - 4CCCD7A86E86CB86C48E303C /* Copy Pods Resources */, ); buildRules = ( ); @@ -643,11 +641,9 @@ isa = PBXNativeTarget; buildConfigurationList = 88A25F2819D8DEC500924534 /* Build configuration list for PBXNativeTarget "JSQMessagesTests" */; buildPhases = ( - F4044DAC71D69462CA8CAE98 /* Check Pods Manifest.lock */, 88A25F1719D8DEC400924534 /* Sources */, 88A25F1819D8DEC400924534 /* Frameworks */, 88A25F1919D8DEC400924534 /* Resources */, - F6B484334A138916FC111868 /* Copy Pods Resources */, ); buildRules = ( ); @@ -665,7 +661,7 @@ 88A25EFA19D8DEC400924534 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0600; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = "Hexed Bits"; TargetAttributes = { 88A25F0119D8DEC400924534 = { @@ -716,10 +712,12 @@ 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 */, @@ -737,69 +735,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 3AF3068570D5C74873D84E30 /* Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - 4CCCD7A86E86CB86C48E303C /* Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - F4044DAC71D69462CA8CAE98 /* Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - F6B484334A138916FC111868 /* Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-JSQMessagesTests/Pods-JSQMessagesTests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 88A25EFE19D8DEC400924534 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -811,6 +746,8 @@ 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 */, @@ -831,7 +768,9 @@ 88A25FE019D8E0C400924534 /* DemoModelData.m in Sources */, 88A25F3C19D8DF2500924534 /* main.m in Sources */, 88A25F3719D8DF2500924534 /* AppDelegate.m in Sources */, - 886FFD2E19E9A65D00EB8485 /* UIDevice+JSQMessages.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 */, @@ -839,12 +778,10 @@ 88A25FDF19D8E0C400924534 /* DemoMessagesViewController.m in Sources */, 88A25FB719D8E01A00924534 /* UIColor+JSQMessages.m in Sources */, 886C33FD19F4371E006B4997 /* JSQVideoMediaItem.m in Sources */, - 88A25FBA19D8E01A00924534 /* JSQMessagesKeyboardController.m in Sources */, 88A25FC019D8E01A00924534 /* JSQMessagesToolbarButtonFactory.m in Sources */, 88A25FC219D8E01A00924534 /* JSQMessagesCollectionViewFlowLayoutInvalidationContext.m in Sources */, 88A25FE119D8E0C400924534 /* TableViewController.m in Sources */, 88A25FBD19D8E01A00924534 /* JSQMessagesAvatarImageFactory.m in Sources */, - 88A25FB519D8E01A00924534 /* JSQSystemSoundPlayer+JSQMessages.m in Sources */, 8873B60C1AB7B244006DF9AC /* NSBundle+JSQMessages.m in Sources */, 88A25FD019D8E01A00924534 /* JSQMessagesComposerTextView.m in Sources */, 88A25FC319D8E01A00924534 /* JSQMessagesCollectionViewLayoutAttributes.m in Sources */, @@ -860,6 +797,7 @@ 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 */, @@ -882,7 +820,6 @@ 8873B60E1AB7B63E006DF9AC /* JSQMessagesNSBundleTests.m in Sources */, 88A2600C19D8E18400924534 /* JSQMessagesCollectionViewLayoutAttributesTests.m in Sources */, 88A2600619D8E18400924534 /* JSQMessagesViewControllerTests.m in Sources */, - 88A2600519D8E18400924534 /* JSQMessagesKeyboardControllerTests.m in Sources */, 88A2600819D8E18400924534 /* JSQMessagesBubbleImageFactoryTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -898,10 +835,20 @@ /* 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 = ""; @@ -913,6 +860,7 @@ 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; @@ -922,15 +870,19 @@ 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", @@ -943,7 +895,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -955,6 +907,7 @@ 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; @@ -964,8 +917,10 @@ 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"; @@ -973,13 +928,14 @@ 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 = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -989,17 +945,18 @@ }; 88A25F2619D8DEC500924534 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FC5C727E4CCDA2B95A7BA30C /* Pods.debug.xcconfig */; 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 = 7.0; + 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 = ""; }; @@ -1007,17 +964,18 @@ }; 88A25F2719D8DEC500924534 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 27B7FD1B722B36B26CB3460B /* Pods.release.xcconfig */; 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 = 7.0; + 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 = ""; }; @@ -1025,20 +983,17 @@ }; 88A25F2919D8DEC500924534 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 223FBACE0F24ADEF8B7F3F24 /* Pods-JSQMessagesTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); + 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"; }; @@ -1046,16 +1001,13 @@ }; 88A25F2A19D8DEC500924534 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0844AD596023C7658D39E241 /* Pods-JSQMessagesTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); + 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"; }; diff --git a/JSQMessages.xcodeproj/xcshareddata/xcschemes/JSQMessages.xcscheme b/JSQMessages.xcodeproj/xcshareddata/xcschemes/JSQMessages.xcscheme index 31e067a3d..c27ce9308 100644 --- a/JSQMessages.xcodeproj/xcshareddata/xcschemes/JSQMessages.xcscheme +++ b/JSQMessages.xcodeproj/xcshareddata/xcschemes/JSQMessages.xcscheme @@ -1,6 +1,6 @@ @@ -51,10 +51,10 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -76,17 +76,21 @@ ReferencedContainer = "container:JSQMessages.xcodeproj"> + + - + - + - - diff --git a/JSQMessagesDemo/Base.lproj/Localizable.strings b/JSQMessagesDemo/Base.lproj/Localizable.strings new file mode 100644 index 000000000..5e9685532 --- /dev/null +++ b/JSQMessagesDemo/Base.lproj/Localizable.strings @@ -0,0 +1,33 @@ +// +// 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!"; \ No newline at end of file diff --git a/JSQMessagesDemo/Base.lproj/Main.storyboard b/JSQMessagesDemo/Base.lproj/Main.storyboard index 73245e389..7d6e3f4dc 100644 --- a/JSQMessagesDemo/Base.lproj/Main.storyboard +++ b/JSQMessagesDemo/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -12,10 +12,10 @@ - + - + @@ -25,7 +25,7 @@ - + @@ -51,13 +51,15 @@ - + + + @@ -73,7 +75,7 @@ @@ -88,8 +90,10 @@ + + @@ -105,7 +109,7 @@ @@ -120,8 +124,10 @@ + + @@ -137,7 +143,7 @@ @@ -151,13 +157,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -173,7 +215,7 @@ @@ -188,8 +230,10 @@ + + @@ -205,7 +249,7 @@ @@ -224,8 +268,10 @@ + + @@ -241,7 +287,7 @@ @@ -265,6 +311,7 @@ + @@ -346,9 +393,4 @@ - - - - - diff --git a/JSQMessagesDemo/DemoMessagesViewController.h b/JSQMessagesDemo/DemoMessagesViewController.h index 7012bc9ad..069f057ea 100644 --- a/JSQMessagesDemo/DemoMessagesViewController.h +++ b/JSQMessagesDemo/DemoMessagesViewController.h @@ -35,7 +35,7 @@ -@interface DemoMessagesViewController : JSQMessagesViewController +@interface DemoMessagesViewController : JSQMessagesViewController @property (weak, nonatomic) id delegateModal; diff --git a/JSQMessagesDemo/DemoMessagesViewController.m b/JSQMessagesDemo/DemoMessagesViewController.m index af1da2d07..70bc3ee3e 100644 --- a/JSQMessagesDemo/DemoMessagesViewController.m +++ b/JSQMessagesDemo/DemoMessagesViewController.m @@ -17,7 +17,10 @@ // #import "DemoMessagesViewController.h" +#import "JSQMessagesViewAccessoryButtonDelegate.h" +@interface DemoMessagesViewController () +@end @implementation DemoMessagesViewController @@ -37,20 +40,20 @@ - (void)viewDidLoad [super viewDidLoad]; self.title = @"JSQMessages"; - - /** - * You MUST set your senderId and display name - */ - self.senderId = kJSQDemoAvatarIdSquires; - self.senderDisplayName = kJSQDemoAvatarDisplayNameSquires; - + + 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 */ @@ -65,7 +68,7 @@ - (void)viewDidLoad self.showLoadEarlierMessagesHeader = YES; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage jsq_defaultTypingIndicatorImage] - style:UIBarButtonItemStyleBordered + style:UIBarButtonItemStylePlain target:self action:@selector(receiveMessagePressed:)]; @@ -73,8 +76,12 @@ - (void)viewDidLoad * Register custom menu actions for cells. */ [JSQMessagesCollectionViewCell registerMenuAction:@selector(customAction:)]; - [UIMenuController sharedMenuController].menuItems = @[ [[UIMenuItem alloc] initWithTitle:@"Custom Action" action:@selector(customAction:)] ]; + + /** + * OPT-IN: allow cells to be deleted + */ + [JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)]; /** * Customize your toolbar buttons @@ -82,6 +89,12 @@ - (void)viewDidLoad * 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 @@ -109,6 +122,21 @@ - (void)viewDidAppear:(BOOL)animated +#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 @@ -155,7 +183,7 @@ - (void)receiveMessagePressed:(UIBarButtonItem *)sender /** * Allow typing indicator to show */ - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + 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]; @@ -209,6 +237,18 @@ - (void)receiveMessagePressed:(UIBarButtonItem *)sender 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__); } @@ -233,7 +273,9 @@ - (void)receiveMessagePressed:(UIBarButtonItem *)sender * 2. Add new id object to your data source * 3. Call `finishReceivingMessage` */ - [JSQSystemSoundPlayer jsq_playMessageReceivedSound]; + + // [JSQSystemSoundPlayer jsq_playMessageReceivedSound]; + [self.demoData.messages addObject:newMessage]; [self finishReceivingMessageAnimated:YES]; @@ -265,6 +307,10 @@ - (void)receiveMessagePressed:(UIBarButtonItem *)sender ((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__); } @@ -298,7 +344,8 @@ - (void)didPressSendButton:(UIButton *)button * 2. Add new id object to your data source * 3. Call `finishSendingMessage` */ - [JSQSystemSoundPlayer jsq_playMessageSentSound]; + + // [JSQSystemSoundPlayer jsq_playMessageSentSound]; JSQMessage *message = [[JSQMessage alloc] initWithSenderId:senderId senderDisplayName:senderDisplayName @@ -312,11 +359,13 @@ - (void)didPressSendButton:(UIButton *)button - (void)didPressAccessoryButton:(UIButton *)sender { - UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"Media messages" + [self.inputToolbar.contentView.textView resignFirstResponder]; + + UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"Media messages", nil) delegate:self - cancelButtonTitle:@"Cancel" + cancelButtonTitle:NSLocalizedString(@"Cancel", nil) destructiveButtonTitle:nil - otherButtonTitles:@"Send photo", @"Send location", @"Send video", 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]; } @@ -324,6 +373,7 @@ - (void)didPressAccessoryButton:(UIButton *)sender - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { if (buttonIndex == actionSheet.cancelButtonIndex) { + [self.inputToolbar.contentView.textView becomeFirstResponder]; return; } @@ -345,9 +395,17 @@ - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSIn case 2: [self.demoData addVideoMediaMessage]; break; + + case 3: + [self.demoData addVideoMediaMessageWithThumbnail]; + break; + + case 4: + [self.demoData addAudioMediaMessage]; + break; } - [JSQSystemSoundPlayer jsq_playMessageSentSound]; + // [JSQSystemSoundPlayer jsq_playMessageSentSound]; [self finishSendingMessageAnimated:YES]; } @@ -356,11 +414,24 @@ - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSIn #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 { /** @@ -505,10 +576,16 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection 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 @@ -538,10 +615,10 @@ - (void)customAction:(id)sender { NSLog(@"Custom action received! Sender: %@", sender); - [[[UIAlertView alloc] initWithTitle:@"Custom Action" - message:nil - delegate:nil - cancelButtonTitle:@"OK" + [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Custom Action", nil) + message:nil + delegate:nil + cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] show]; } @@ -622,4 +699,29 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCellAtI 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 diff --git a/JSQMessagesDemo/DemoModelData.h b/JSQMessagesDemo/DemoModelData.h index 626c2de76..658ec8a0a 100644 --- a/JSQMessagesDemo/DemoModelData.h +++ b/JSQMessagesDemo/DemoModelData.h @@ -58,4 +58,8 @@ static NSString * const kJSQDemoAvatarIdWoz = @"309-41802-93823"; - (void)addVideoMediaMessage; +- (void)addVideoMediaMessageWithThumbnail; + +- (void)addAudioMediaMessage; + @end diff --git a/JSQMessagesDemo/DemoModelData.m b/JSQMessagesDemo/DemoModelData.m index 8e5e62e8d..463a5416c 100644 --- a/JSQMessagesDemo/DemoModelData.m +++ b/JSQMessagesDemo/DemoModelData.m @@ -49,20 +49,18 @@ - (instancetype)init * * If you are not using avatars, ignore this. */ - JSQMessagesAvatarImage *jsqImage = [JSQMessagesAvatarImageFactory avatarImageWithUserInitials:@"JSQ" - backgroundColor:[UIColor colorWithWhite:0.85f alpha:1.0f] - textColor:[UIColor colorWithWhite:0.60f alpha:1.0f] - font:[UIFont systemFontOfSize:14.0f] - diameter:kJSQMessagesCollectionViewAvatarSizeDefault]; + JSQMessagesAvatarImageFactory *avatarFactory = [[JSQMessagesAvatarImageFactory alloc] initWithDiameter:kJSQMessagesCollectionViewAvatarSizeDefault]; - JSQMessagesAvatarImage *cookImage = [JSQMessagesAvatarImageFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_cook"] - diameter: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 *jobsImage = [JSQMessagesAvatarImageFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_jobs"] - diameter:kJSQMessagesCollectionViewAvatarSizeDefault]; + JSQMessagesAvatarImage *cookImage = [avatarFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_cook"]]; - JSQMessagesAvatarImage *wozImage = [JSQMessagesAvatarImageFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_woz"] - diameter:kJSQMessagesCollectionViewAvatarSizeDefault]; + JSQMessagesAvatarImage *jobsImage = [avatarFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_jobs"]]; + + JSQMessagesAvatarImage *wozImage = [avatarFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_woz"]]; self.avatars = @{ kJSQDemoAvatarIdSquires : jsqImage, kJSQDemoAvatarIdCook : cookImage, @@ -102,35 +100,36 @@ - (void)loadFakeMessages [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdSquires senderDisplayName:kJSQDemoAvatarDisplayNameSquires date:[NSDate distantPast] - text:@"Welcome to JSQMessages: A messaging UI framework for iOS."], + text:NSLocalizedString(@"Welcome to JSQMessages: A messaging UI framework for iOS.", nil)], [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdWoz senderDisplayName:kJSQDemoAvatarDisplayNameWoz date:[NSDate distantPast] - text:@"It is simple, elegant, and easy to use. There are super sweet default settings, but you can customize like crazy."], + 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:@"It even has data detectors. You can call me tonight. My cell number is 123-456-7890. My website is www.hexedbits.com."], + 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:@"JSQMessagesViewController is nearly an exact replica of the iOS Messages App. And perhaps, better."], + 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:@"It is unit-tested, free, open-source, and documented."], + text:NSLocalizedString(@"It is unit-tested, free, open-source, and documented.", nil)], [[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdSquires senderDisplayName:kJSQDemoAvatarDisplayNameSquires date:[NSDate date] - text:@"Now with media messages!"], + text:NSLocalizedString(@"Now with media messages!", nil)], nil]; [self addPhotoMediaMessage]; + [self addAudioMediaMessage]; /** * Setting to load extra messages for testing/demo @@ -156,6 +155,17 @@ - (void)loadFakeMessages } } +- (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"]]; @@ -190,4 +200,16 @@ - (void)addVideoMediaMessage [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 diff --git a/JSQMessagesDemo/DemoSettingsViewController.h b/JSQMessagesDemo/DemoSettingsViewController.h index 4b6ddaee5..b1bbdfae2 100644 --- a/JSQMessagesDemo/DemoSettingsViewController.h +++ b/JSQMessagesDemo/DemoSettingsViewController.h @@ -39,4 +39,6 @@ @property (weak, nonatomic) IBOutlet UISwitch *springySwitch; +@property (weak, nonatomic) IBOutlet UISwitch *accessoryButtonSwitch; + @end diff --git a/JSQMessagesDemo/DemoSettingsViewController.m b/JSQMessagesDemo/DemoSettingsViewController.m index 857cc494f..e4075921d 100644 --- a/JSQMessagesDemo/DemoSettingsViewController.m +++ b/JSQMessagesDemo/DemoSettingsViewController.m @@ -36,6 +36,7 @@ - (void)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]; @@ -47,7 +48,6 @@ - (IBAction)didTapSwitch:(UISwitch *)sender { if (sender == self.extraMessagesSwitch) { [NSUserDefaults saveExtraMessagesSetting:sender.on]; - } else if (sender == self.longMessageSwitch) { [NSUserDefaults saveLongMessageSetting:sender.on]; @@ -55,6 +55,9 @@ - (IBAction)didTapSwitch:(UISwitch *)sender 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]; } diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Contents.json b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Contents.json index 67fc78bfb..ac3b9ecc3 100644 --- a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "size" : "29x29", "idiom" : "iphone", @@ -36,6 +46,16 @@ "filename" : "Icon-180.png", "scale" : "3x" }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, { "size" : "29x29", "idiom" : "ipad", @@ -71,6 +91,12 @@ "idiom" : "ipad", "filename" : "Icon-76@2x.png", "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "icon167.png", + "scale" : "2x" } ], "info" : { diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-120-1.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-120-1.png index 8755a01ef..499b308de 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-120-1.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-120-1.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-120.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-120.png index 8755a01ef..499b308de 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-120.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-120.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-180.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-180.png index a21cca223..89f667a81 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-180.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-180.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png index 2e0962bd9..da561c898 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png index 513ac7809..01bfdd37b 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-87.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-87.png index d985b9b42..cfb5f48f1 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-87.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-87.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small.png index 56ba08479..e82d15ea2 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png index 4e72ce6d9..6ba17f3ef 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png index 4e72ce6d9..6ba17f3ef 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7.png index b51ac1267..2fff91001 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7@2x-1.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7@2x-1.png index f384eb7b3..1f80b4a64 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7@2x-1.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7@2x-1.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7@2x.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7@2x.png index f384eb7b3..1f80b4a64 100644 Binary files a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7@2x.png and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-iOS7@2x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/icon167.png b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/icon167.png new file mode 100644 index 000000000..e10dc2023 Binary files /dev/null and b/JSQMessagesDemo/Images.xcassets/AppIcon.appiconset/icon167.png differ diff --git a/JSQMessagesDemo/Images.xcassets/Contents.json b/JSQMessagesDemo/Images.xcassets/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/JSQMessagesDemo/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook.png b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook.png index dc8615eab..ad28f56b0 100644 Binary files a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook.png and b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook.png differ diff --git a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook@2x.png b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook@2x.png index 4e2b03659..0e484c588 100644 Binary files a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook@2x.png and b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook@2x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook@3x.png b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook@3x.png index 8a01e97a2..1bceca3c0 100644 Binary files a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook@3x.png and b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_cook.imageset/demo_avatar_cook@3x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs.png b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs.png index 356a3e2ec..4911c3fd7 100644 Binary files a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs.png and b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs.png differ diff --git a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs@2x.png b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs@2x.png index 82249ceb2..b6c6e5d7e 100644 Binary files a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs@2x.png and b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs@2x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs@3x.png b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs@3x.png index 267bb5f90..fd7506c20 100644 Binary files a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs@3x.png and b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_jobs.imageset/demo_avatar_jobs@3x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/demo_avatar_woz.png b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/demo_avatar_woz.png index d72851267..3b9b8be83 100644 Binary files a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/demo_avatar_woz.png and b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/demo_avatar_woz.png differ diff --git a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/demo_avatar_woz@3x.png b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/demo_avatar_woz@3x.png index a00582cf7..18ced9168 100644 Binary files a/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/demo_avatar_woz@3x.png and b/JSQMessagesDemo/Images.xcassets/DemoAvatars/demo_avatar_woz.imageset/demo_avatar_woz@3x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_47inch.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_47inch.png index dced2d78a..b4392bad8 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_47inch.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_47inch.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_55inch.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_55inch.png index cf9b89b8d..a26d36a39 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_55inch.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_55inch.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_55inch_landscape.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_55inch_landscape.png index b3c1ba9ad..0773e47ca 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_55inch_landscape.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_55inch_landscape.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad.png index bf94b786b..0d8af53dc 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad@2x.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad@2x.png index dc0f7b3fc..6819bb9bb 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad@2x.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad@2x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad@2x~landscape.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad@2x~landscape.png index f80be19da..420a349ca 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad@2x~landscape.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad@2x~landscape.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad~landscape.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad~landscape.png index 82e01bda1..de1cc30c9 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad~landscape.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/jsq_messages_splash_ipad~landscape.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/slpash_iphone4inch.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/slpash_iphone4inch.png index dbfdf535d..5b3ecb6f3 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/slpash_iphone4inch.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/slpash_iphone4inch.png differ diff --git a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/splash_iphone35inch.png b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/splash_iphone35inch.png index d7030ca14..cbfae2507 100644 Binary files a/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/splash_iphone35inch.png and b/JSQMessagesDemo/Images.xcassets/LaunchImage.launchimage/splash_iphone35inch.png differ diff --git a/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate.png b/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate.png index b9a720fd5..2cdc23e82 100644 Binary files a/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate.png and b/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate.png differ diff --git a/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate@2x.png b/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate@2x.png index 96f741f96..6150a7863 100644 Binary files a/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate@2x.png and b/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate@2x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate@3x.png b/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate@3x.png index 11b1691f9..f49e27aa1 100644 Binary files a/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate@3x.png and b/JSQMessagesDemo/Images.xcassets/goldengate.imageset/goldengate@3x.png differ diff --git a/JSQMessagesDemo/Info.plist b/JSQMessagesDemo/Info.plist index 4ccd513da..b1fd8937f 100644 --- a/JSQMessagesDemo/Info.plist +++ b/JSQMessagesDemo/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.hexedbits.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 7.0.2 + 7.3.4 CFBundleSignature ???? CFBundleVersion - 7.0.2 + 7.3.4 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/JSQMessagesDemo/NSUserDefaults+DemoSettings.h b/JSQMessagesDemo/NSUserDefaults+DemoSettings.h index 7f15bef17..4fdbf2a96 100644 --- a/JSQMessagesDemo/NSUserDefaults+DemoSettings.h +++ b/JSQMessagesDemo/NSUserDefaults+DemoSettings.h @@ -38,4 +38,7 @@ + (void)saveIncomingAvatarSetting:(BOOL)value; + (BOOL)incomingAvatarSetting; ++ (void)saveAccessoryButtonForMediaMessages:(BOOL)value; ++ (BOOL)accessoryButtonForMediaMessages; + @end diff --git a/JSQMessagesDemo/NSUserDefaults+DemoSettings.m b/JSQMessagesDemo/NSUserDefaults+DemoSettings.m index ad2bd2151..69d5b4735 100644 --- a/JSQMessagesDemo/NSUserDefaults+DemoSettings.m +++ b/JSQMessagesDemo/NSUserDefaults+DemoSettings.m @@ -18,13 +18,13 @@ #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 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) @@ -88,4 +88,13 @@ + (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 diff --git a/JSQMessagesDemo/TableViewController.m b/JSQMessagesDemo/TableViewController.m index 85951ca21..abf579465 100644 --- a/JSQMessagesDemo/TableViewController.m +++ b/JSQMessagesDemo/TableViewController.m @@ -38,16 +38,23 @@ - (void)viewWillAppear:(BOOL)animated - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 3; + return 4; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (section == 2) { - return 1; + switch (section) { + case 0: + return 2; + case 1: + return 2; + case 2: + return 1; + case 3: + return 1; + default: + return 0; } - - return 2; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath @@ -58,7 +65,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } - + if (indexPath.section == 0) { switch (indexPath.row) { case 0: @@ -86,7 +93,14 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; } } - + else if (indexPath.section == 3) { + switch (indexPath.row) { + case 0: + cell.textLabel.text = @"Push view 2 levels"; + break; + } + } + return cell; } @@ -97,6 +111,8 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte return @"Presentation"; case 2: return @"Demo options"; + case 3: + return @"Other testing"; default: return nil; } @@ -104,7 +120,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { - return (section == [tableView numberOfSections] - 1) ? @"Copyright © 2014\nJesse Squires\nMIT License" : nil; + return (section == 2) ? @"Copyright © 2015\nJesse Squires\nMIT License" : nil; } #pragma mark - Table view delegate @@ -146,6 +162,19 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath 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 diff --git a/JSQMessagesDemo/he.lproj/Localizable.strings b/JSQMessagesDemo/he.lproj/Localizable.strings new file mode 100644 index 000000000..5b6296896 --- /dev/null +++ b/JSQMessagesDemo/he.lproj/Localizable.strings @@ -0,0 +1,40 @@ +// +// 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!" = "וישנה גם תמיכה בהודעות מדיה"; + + + + + + + + diff --git a/JSQMessagesDemo/he.lproj/Main.strings b/JSQMessagesDemo/he.lproj/Main.strings new file mode 100644 index 000000000..b0207f8e6 --- /dev/null +++ b/JSQMessagesDemo/he.lproj/Main.strings @@ -0,0 +1,42 @@ + +/* 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" = "הודעות"; diff --git a/JSQMessagesDemo/jsq_messages_sample.m4a b/JSQMessagesDemo/jsq_messages_sample.m4a new file mode 100644 index 000000000..d1f98ab21 Binary files /dev/null and b/JSQMessagesDemo/jsq_messages_sample.m4a differ diff --git a/JSQMessagesTests/CategoryTests/JSQMessagesUIImageTests.m b/JSQMessagesTests/CategoryTests/JSQMessagesUIImageTests.m index ab5ac4e18..a14c435d5 100644 --- a/JSQMessagesTests/CategoryTests/JSQMessagesUIImageTests.m +++ b/JSQMessagesTests/CategoryTests/JSQMessagesUIImageTests.m @@ -33,8 +33,6 @@ - (void)testImageMasking XCTAssertTrue(CGSizeEqualToSize(img.size, imgMasked.size), @"Image sizes should be equal"); XCTAssertEqual(img.scale, imgMasked.scale, @"Image scales should be equal"); - - XCTAssertThrows([img jsq_imageMaskedWithColor:nil], @"Should throw when passing nil color"); } - (void)testImageAssets @@ -60,8 +58,10 @@ - (void)testImageAssets XCTAssertNotNil([UIImage jsq_defaultAccessoryImage]); XCTAssertNotNil([UIImage jsq_defaultTypingIndicatorImage]); - + XCTAssertNotNil([UIImage jsq_defaultPlayImage]); + + XCTAssertNotNil([UIImage jsq_shareActionImage]); } @end diff --git a/JSQMessagesTests/ControllerTests/JSQMessagesKeyboardControllerTests.m b/JSQMessagesTests/ControllerTests/JSQMessagesKeyboardControllerTests.m deleted file mode 100644 index 7937152e2..000000000 --- a/JSQMessagesTests/ControllerTests/JSQMessagesKeyboardControllerTests.m +++ /dev/null @@ -1,46 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// MIT License -// Copyright (c) 2014 Jesse Squires -// http://opensource.org/licenses/MIT -// - -#import - -#import "JSQMessagesKeyboardController.h" - - -@interface JSQMessagesKeyboardControllerTests : XCTestCase -@end - - -@implementation JSQMessagesKeyboardControllerTests - -- (void)setUp -{ - [super setUp]; -} - -- (void)tearDown -{ - [super tearDown]; -} - -- (void)testKeyboardControllerInit -{ - JSQMessagesKeyboardController *keyboardController = [[JSQMessagesKeyboardController alloc] initWithTextView:[UITextView new] - contextView:[UIView new] - panGestureRecognizer:[UIPanGestureRecognizer new] - delegate:nil]; - XCTAssertNotNil(keyboardController, @"Keyboard controller should not be nil"); - - XCTAssertThrows([[JSQMessagesKeyboardController alloc] initWithTextView:nil - contextView:nil - panGestureRecognizer:nil - delegate:nil], @"Invalid init should throw"); -} - -@end diff --git a/JSQMessagesTests/ControllerTests/JSQMessagesViewControllerTests.m b/JSQMessagesTests/ControllerTests/JSQMessagesViewControllerTests.m index 7305de7e8..ff39164ae 100644 --- a/JSQMessagesTests/ControllerTests/JSQMessagesViewControllerTests.m +++ b/JSQMessagesTests/ControllerTests/JSQMessagesViewControllerTests.m @@ -10,8 +10,6 @@ #import -#import - #import "JSQMessagesViewController.h" #import "DemoMessagesViewController.h" @@ -48,7 +46,10 @@ - (void)testJSQMessagesViewControllerInit XCTAssertNotNil(nib, @"Nib should not be nil"); JSQMessagesViewController *vc = [JSQMessagesViewController messagesViewController]; - [vc view]; + + [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"); @@ -66,7 +67,10 @@ - (void)testJSQMessagesViewControllerInit - (void)testJSQMessagesViewControllerSubclassInitProgramatically { DemoMessagesViewController *demoVC = [DemoMessagesViewController messagesViewController]; - [demoVC view]; + + [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"); @@ -80,7 +84,10 @@ - (void)testJSQMessagesViewControllerSubclassInitStoryboards XCTAssertNotNil(mainSB, @"Storyboard should not be nil"); DemoMessagesViewController *demoVC = [mainSB instantiateViewControllerWithIdentifier:@"DemoVC"]; - [demoVC view]; + + [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"); @@ -88,14 +95,4 @@ - (void)testJSQMessagesViewControllerSubclassInitStoryboards XCTAssertNotNil(demoVC.inputToolbar, @"Input toolbar should not be nil"); } -- (void)testViewConfiguration -{ - JSQMessagesViewController *vc = [JSQMessagesViewController messagesViewController]; - - id mockVC = [OCMockObject partialMockForObject:vc]; - [[mockVC expect] jsq_configureMessagesViewController]; - [vc view]; - [mockVC verify]; -} - @end diff --git a/JSQMessagesTests/FactoryTests/JSQMessagesAvatarImageFactoryTests.m b/JSQMessagesTests/FactoryTests/JSQMessagesAvatarImageFactoryTests.m index b63a3c044..9cd2fb5f0 100644 --- a/JSQMessagesTests/FactoryTests/JSQMessagesAvatarImageFactoryTests.m +++ b/JSQMessagesTests/FactoryTests/JSQMessagesAvatarImageFactoryTests.m @@ -14,6 +14,10 @@ @interface JSQMessagesAvatarImageFactoryTests : XCTestCase + +@property (strong, nonatomic) JSQMessagesAvatarImageFactory *factory; +@property (assign, nonatomic) NSUInteger avatarDiameter; + @end @@ -22,11 +26,19 @@ @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 @@ -34,39 +46,36 @@ - (void)testAvatarImage UIImage *image = [UIImage imageNamed:@"demo_avatar_jobs"]; XCTAssertNotNil(image, @"Image should not be nil"); - CGFloat diameter = 50.0f; - JSQMessagesAvatarImage *avatar = [JSQMessagesAvatarImageFactory avatarImageWithPlaceholder:image diameter:diameter]; + JSQMessagesAvatarImage *avatar = [self.factory avatarImageWithPlaceholder:image]; XCTAssertNotNil(avatar, @"Avatar should not be nil"); - XCTAssertTrue(CGSizeEqualToSize(avatar.avatarPlaceholderImage.size, CGSizeMake(diameter, diameter)), @"Avatar size should be equal to diameter"); + 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 = [JSQMessagesAvatarImageFactory circularAvatarImage:image withDiameter:diameter]; - XCTAssertTrue(CGSizeEqualToSize(avatar.avatarImage.size, CGSizeMake(diameter, diameter)), @"Avatar size should be equal to diameter"); + 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 = [JSQMessagesAvatarImageFactory circularAvatarHighlightedImage:image withDiameter:diameter]; - XCTAssertTrue(CGSizeEqualToSize(avatar.avatarHighlightedImage.size, CGSizeMake(diameter, diameter)), @"Avatar size should be equal to diameter"); + 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 { - CGFloat diameter = 50.0f; - JSQMessagesAvatarImage *avatar = [JSQMessagesAvatarImageFactory avatarImageWithUserInitials:@"JSQ" - backgroundColor:[UIColor lightGrayColor] - textColor:[UIColor darkGrayColor] - font:[UIFont systemFontOfSize:13.0f] - diameter:diameter]; + 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, CGSizeMake(diameter, diameter)), @"Avatar size should be equal to diameter"); + 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, CGSizeMake(diameter, diameter)), @"Avatar size should be equal to diameter"); + 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, CGSizeMake(diameter, diameter)), @"Avatar size should be equal to diameter"); + 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"); } diff --git a/JSQMessagesTests/FactoryTests/JSQMessagesBubbleImageFactoryTests.m b/JSQMessagesTests/FactoryTests/JSQMessagesBubbleImageFactoryTests.m index 1e2249e33..3cc25b2d4 100644 --- a/JSQMessagesTests/FactoryTests/JSQMessagesBubbleImageFactoryTests.m +++ b/JSQMessagesTests/FactoryTests/JSQMessagesBubbleImageFactoryTests.m @@ -36,15 +36,22 @@ - (void)tearDown [super tearDown]; } -- (void)testOutgoingMessageBubbleImageView +- (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"); @@ -65,7 +72,13 @@ - (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); @@ -74,28 +87,30 @@ - (void)testIncomingMessageBubbleImageView 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"); + 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, UIImageOrientationUpMirrored, @"Image orientation should be flipped"); + 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)testCustomOutgoingMessageBubbleImageView +- (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]; + + 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"); @@ -109,22 +124,41 @@ - (void)testCustomOutgoingMessageBubbleImageView 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]; + 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"); @@ -132,4 +166,27 @@ - (void)testCustomIncomingMessageBubbleImageView 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 diff --git a/JSQMessagesTests/FactoryTests/JSQMessagesTimestampFormatterTests.m b/JSQMessagesTests/FactoryTests/JSQMessagesTimestampFormatterTests.m index cbd08e755..e41bbc721 100644 --- a/JSQMessagesTests/FactoryTests/JSQMessagesTimestampFormatterTests.m +++ b/JSQMessagesTests/FactoryTests/JSQMessagesTimestampFormatterTests.m @@ -40,11 +40,11 @@ - (void)testTimestampFormatterInit NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; paragraphStyle.alignment = NSTextAlignmentCenter; - NSDictionary *dateAttrs = @{ NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0f], + NSDictionary *dateAttrs = @{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : color, NSParagraphStyleAttributeName : paragraphStyle }; - NSDictionary *timeAttrs = @{ NSFontAttributeName : [UIFont systemFontOfSize:12.0f], + NSDictionary *timeAttrs = @{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : color, NSParagraphStyleAttributeName : paragraphStyle }; @@ -98,7 +98,7 @@ - (void)testRelativeDataForDate NSString *relativeDateString = [[JSQMessagesTimestampFormatter sharedFormatter] relativeDateForDate:date]; - XCTAssertEqualObjects(relativeDateString, @"Today", @"Relative date string shoudl return expected value"); + XCTAssertEqualObjects(relativeDateString, @"Today", @"Relative date string should return expected value"); } @end diff --git a/JSQMessagesTests/FactoryTests/JSQMessagesToolbarButtonFactoryTests.m b/JSQMessagesTests/FactoryTests/JSQMessagesToolbarButtonFactoryTests.m index 93e7b90ce..2f44b7277 100644 --- a/JSQMessagesTests/FactoryTests/JSQMessagesToolbarButtonFactoryTests.m +++ b/JSQMessagesTests/FactoryTests/JSQMessagesToolbarButtonFactoryTests.m @@ -17,21 +17,38 @@ @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 = [JSQMessagesToolbarButtonFactory defaultSendButtonItem]; + 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 = [JSQMessagesToolbarButtonFactory defaultAccessoryButtonItem]; + 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 diff --git a/JSQMessagesTests/Info.plist b/JSQMessagesTests/Info.plist index 8551077d7..ba72822e8 100644 --- a/JSQMessagesTests/Info.plist +++ b/JSQMessagesTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.hexedbits.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/JSQMessagesTests/LayoutTests/JSQMessagesCollectionViewLayoutAttributesTests.m b/JSQMessagesTests/LayoutTests/JSQMessagesCollectionViewLayoutAttributesTests.m index 8460ba4a1..adbec0ce3 100644 --- a/JSQMessagesTests/LayoutTests/JSQMessagesCollectionViewLayoutAttributesTests.m +++ b/JSQMessagesTests/LayoutTests/JSQMessagesCollectionViewLayoutAttributesTests.m @@ -33,7 +33,7 @@ - (void)testLayoutAttributesInitAndIsEqual { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; JSQMessagesCollectionViewLayoutAttributes *attrs = [JSQMessagesCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; - attrs.messageBubbleFont = [UIFont systemFontOfSize:15.0f]; + 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); diff --git a/JSQMessagesTests/ModelTests/JSQAudioMediaItemTests.m b/JSQMessagesTests/ModelTests/JSQAudioMediaItemTests.m new file mode 100644 index 000000000..d91afd0c3 --- /dev/null +++ b/JSQMessagesTests/ModelTests/JSQAudioMediaItemTests.m @@ -0,0 +1,77 @@ +// +// 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 diff --git a/JSQMessagesTests/ModelTests/JSQLocationMediaItemTests.m b/JSQMessagesTests/ModelTests/JSQLocationMediaItemTests.m index cf9c80a38..7438387d3 100644 --- a/JSQMessagesTests/ModelTests/JSQLocationMediaItemTests.m +++ b/JSQMessagesTests/ModelTests/JSQLocationMediaItemTests.m @@ -12,6 +12,7 @@ #import "JSQLocationMediaItem.h" +#import @interface JSQLocationMediaItemTests : XCTestCase @@ -54,11 +55,21 @@ - (void)testMediaDataProtocol [expectation fulfill]; }]; - [self waitForExpectationsWithTimeout:5 handler:^(NSError *error) { + [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 diff --git a/JSQMessagesTests/ModelTests/JSQMessageMediaTests.m b/JSQMessagesTests/ModelTests/JSQMessageMediaTests.m index 7da1352d8..f8885701a 100644 --- a/JSQMessagesTests/ModelTests/JSQMessageMediaTests.m +++ b/JSQMessagesTests/ModelTests/JSQMessageMediaTests.m @@ -10,7 +10,7 @@ #import -#import +//#import #import "JSQMessage.h" @@ -44,7 +44,7 @@ @interface JSQMessageMediaTests : XCTestCase @property (strong, nonatomic) NSString *senderId; @property (strong, nonatomic) NSString *senderDisplayName; @property (strong, nonatomic) NSDate *date; -@property (strong, nonatomic) id mockMediaData; +@property (strong, nonatomic) FakeMedia *fakeMediaData; @end @@ -57,9 +57,7 @@ - (void)setUp self.senderId = @"324543-43556-212343"; self.senderDisplayName = @"Jesse Squires"; self.date = [NSDate date]; - - self.mockMediaData = [OCMockObject mockForProtocol:@protocol(JSQMessageMediaData)]; - [[self.mockMediaData stub] mediaHash]; + self.fakeMediaData = [FakeMedia new]; } - (void)tearDown @@ -67,7 +65,7 @@ - (void)tearDown self.senderId = nil; self.senderDisplayName = nil; self.date = nil; - self.mockMediaData = nil; + self.fakeMediaData = nil; [super tearDown]; } @@ -76,22 +74,16 @@ - (void)testMediaMessageInit JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date - media:self.mockMediaData]; + media:self.fakeMediaData]; XCTAssertNotNil(msg, @"Message should not be nil"); } -- (void)testMediaMessageInvalidInit -{ - XCTAssertThrows([[JSQMessage alloc] init], @"Invalid init should throw"); - XCTAssertThrows([[JSQMessage alloc] initWithSenderId:nil senderDisplayName:nil date:nil media:nil], @"Invalid init should throw"); -} - - (void)testMediaMessageIsEqual { JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date - media:self.mockMediaData]; + media:self.fakeMediaData]; JSQMessage *copy = [msg copy]; XCTAssertEqualObjects(msg, copy, @"Copied messages should be equal"); diff --git a/JSQMessagesTests/ModelTests/JSQMessageTextTests.m b/JSQMessagesTests/ModelTests/JSQMessageTextTests.m index 74783d17f..8dbf39487 100644 --- a/JSQMessagesTests/ModelTests/JSQMessageTextTests.m +++ b/JSQMessagesTests/ModelTests/JSQMessageTextTests.m @@ -58,12 +58,6 @@ - (void)testTextMessageInit XCTAssertNotNil(msg, @"Message should not be nil"); } -- (void)testTextMessageInvalidInit -{ - XCTAssertThrows([[JSQMessage alloc] init], @"Invalid init should throw"); - XCTAssertThrows([[JSQMessage alloc] initWithSenderId:nil senderDisplayName:nil date:nil text:nil], @"Invalid init should throw"); -} - - (void)testTextMessageIsEqual { JSQMessage *msg = [[JSQMessage alloc] initWithSenderId:self.senderId diff --git a/JSQMessagesTests/ModelTests/JSQMessagesAvatarImageTests.m b/JSQMessagesTests/ModelTests/JSQMessagesAvatarImageTests.m index d03df7ee1..9a435fc4c 100644 --- a/JSQMessagesTests/ModelTests/JSQMessagesAvatarImageTests.m +++ b/JSQMessagesTests/ModelTests/JSQMessagesAvatarImageTests.m @@ -20,23 +20,6 @@ @interface JSQMessagesAvatarImageTests : XCTestCase @implementation JSQMessagesAvatarImageTests -- (void)setUp -{ - [super setUp]; -} - -- (void)tearDown -{ - [super tearDown]; -} - -- (void)testInitInvalid -{ - XCTAssertThrows([[JSQMessagesAvatarImage alloc] init], @"Invalid init should throw"); - XCTAssertThrows([JSQMessagesAvatarImage avatarImageWithPlaceholder:nil], @"Invalid init should throw"); - XCTAssertThrows([[JSQMessagesAvatarImage alloc] initWithAvatarImage:nil highlightedImage:nil placeholderImage:nil], @"Invalid init should throw"); -} - - (void)testInitValid { UIImage *mockImage = [UIImage imageNamed:@"demo_avatar_jobs"]; diff --git a/JSQMessagesTests/ModelTests/JSQMessagesBubbleImageTests.m b/JSQMessagesTests/ModelTests/JSQMessagesBubbleImageTests.m index d10f5f9d6..c5ab391c7 100644 --- a/JSQMessagesTests/ModelTests/JSQMessagesBubbleImageTests.m +++ b/JSQMessagesTests/ModelTests/JSQMessagesBubbleImageTests.m @@ -22,22 +22,6 @@ @interface JSQMessagesBubbleImageTests : XCTestCase @implementation JSQMessagesBubbleImageTests -- (void)setUp -{ - [super setUp]; -} - -- (void)tearDown -{ - [super tearDown]; -} - -- (void)testInitInvalid -{ - XCTAssertThrows([[JSQMessagesBubbleImage alloc] init], @"Invalid init should throw"); - XCTAssertThrows([[JSQMessagesBubbleImage alloc] initWithMessageBubbleImage:nil highlightedImage:nil], @"Invalid init should throw"); -} - - (void)testInitValid { UIImage *mockImage = [UIImage jsq_bubbleCompactImage]; diff --git a/JSQMessagesTests/ModelTests/JSQPhotoMediaItemTests.m b/JSQMessagesTests/ModelTests/JSQPhotoMediaItemTests.m index 3e568b7b5..f2d5a23c3 100644 --- a/JSQMessagesTests/ModelTests/JSQPhotoMediaItemTests.m +++ b/JSQMessagesTests/ModelTests/JSQPhotoMediaItemTests.m @@ -12,6 +12,7 @@ #import "JSQPhotoMediaItem.h" +#import @interface JSQPhotoMediaItemTests : XCTestCase @@ -73,4 +74,13 @@ - (void)testMediaDataProtocol 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 diff --git a/JSQMessagesTests/ViewTests/JSQMessagesCollectionViewTests.m b/JSQMessagesTests/ViewTests/JSQMessagesCollectionViewTests.m index ae9644e5b..a368f7320 100644 --- a/JSQMessagesTests/ViewTests/JSQMessagesCollectionViewTests.m +++ b/JSQMessagesTests/ViewTests/JSQMessagesCollectionViewTests.m @@ -39,7 +39,7 @@ - (void)testCollectionViewInit XCTAssertNotNil(view, @"Collection view should not be nil"); XCTAssertEqualObjects(view.backgroundColor, [UIColor whiteColor], @"Property should be equal to default value"); - XCTAssertEqual(view.keyboardDismissMode, UIScrollViewKeyboardDismissModeNone, @"Property should be equal to default value"); + XCTAssertEqual(view.keyboardDismissMode, UIScrollViewKeyboardDismissModeInteractive, @"Property should be equal to default value"); XCTAssertEqual(view.alwaysBounceVertical, YES, @"Property should be equal to default value"); XCTAssertEqual(view.bounces, YES, @"Property should be equal to default value"); } diff --git a/JSQMessagesTests/ViewTests/JSQMessagesComposerTextViewTests.m b/JSQMessagesTests/ViewTests/JSQMessagesComposerTextViewTests.m index 66bdcb8e9..9ba0f0ecb 100644 --- a/JSQMessagesTests/ViewTests/JSQMessagesComposerTextViewTests.m +++ b/JSQMessagesTests/ViewTests/JSQMessagesComposerTextViewTests.m @@ -65,6 +65,20 @@ - (void)testComposerTextViewInit 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 diff --git a/JSQMessagesTests/ViewTests/JSQMessagesInputToolbarTests.m b/JSQMessagesTests/ViewTests/JSQMessagesInputToolbarTests.m index 6ef286153..d9935eacc 100644 --- a/JSQMessagesTests/ViewTests/JSQMessagesInputToolbarTests.m +++ b/JSQMessagesTests/ViewTests/JSQMessagesInputToolbarTests.m @@ -12,6 +12,7 @@ #import "JSQMessagesViewController.h" #import "JSQMessagesInputToolbar.h" +#import "DemoMessagesViewController.h" @interface JSQMessagesInputToolbarTests : XCTestCase @@ -34,11 +35,37 @@ - (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.sendButtonOnRight, YES, @"Property should be equal to default value"); + 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 diff --git a/JSQMessagesViewController.podspec b/JSQMessagesViewController.podspec index 70283ac1b..009cad392 100644 --- a/JSQMessagesViewController.podspec +++ b/JSQMessagesViewController.podspec @@ -1,26 +1,19 @@ Pod::Spec.new do |s| s.name = 'JSQMessagesViewController' - s.version = '7.0.2' + s.version = '7.3.5' s.summary = 'An elegant messages UI library for iOS.' - s.homepage = 'http://jessesquires.github.io/JSQMessagesViewController' s.license = 'MIT' s.platform = :ios, '7.0' - s.author = { 'Jesse Squires' => 'jesse.squires.developer@gmail.com' } - s.social_media_url = 'https://twitter.com/jesse_squires' - - s.screenshots = ['https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot0.png', - 'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot1.png', - 'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot2.png', - 'https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot3.png'] + s.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', 'UIKit', 'Foundation' + + s.frameworks = 'QuartzCore', 'CoreGraphics', 'CoreLocation', 'MapKit', 'MobileCoreServices', 'AVFoundation' s.requires_arc = true - s.dependency 'JSQSystemSoundPlayer', '~> 2.0.1' + s.deprecated = true end diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Base.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Base.lproj/JSQMessages.strings index 53dff590d..0e8cae02d 100644 --- a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Base.lproj/JSQMessages.strings +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Base.lproj/JSQMessages.strings @@ -27,3 +27,11 @@ "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"; \ No newline at end of file diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min.png index 5e6f44473..62a480ea2 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min@2x.png index cdeb1f5b5..13cfba622 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min@3x.png index 6fd3e90cb..615dcab19 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless.png index db260340f..9959084c7 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless@2x.png index 2bfe4321a..4702fa0ec 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless@3x.png index 1811c2200..09d77fcb2 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_min_tailless@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular.png index eea6cbe88..f4e7f93d1 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular@2x.png index 88baedb75..2322a614b 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular@3x.png index 7ab50cf44..4081d1070 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_regular@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked.png index 6b0d77f9b..6752256bf 100755 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked@2x.png index fdaab0063..0cfaca58b 100755 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked@3x.png index 41ce17815..4c80a0cf2 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless.png index 67f03a947..73973294f 100755 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless@2x.png index 158c18314..27e8532dd 100755 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless@3x.png index 170c16445..1f0235cf0 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_stroked_tailless@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless.png index 9445eec00..637b93d99 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless@2x.png index 297764dd5..3ed029e28 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless@3x.png index c968c39ec..9a5b9fdef 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/bubble_tailless@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip.png index 8506bbe00..ea04a1bc4 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip@2x.png index 58ba9f5f7..1ba72fb4c 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip@3x.png index caba66250..44b0d2b04 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/clip@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause.png new file mode 100644 index 000000000..04c7c8099 Binary files /dev/null and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause@2x.png new file mode 100644 index 000000000..0dddb7b67 Binary files /dev/null and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause@3x.png new file mode 100644 index 000000000..bcbbd8f71 Binary files /dev/null and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/pause@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play.png index 3bd7045bf..6bf97fcf9 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play@2x.png index c769e68f6..0907493c5 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play@3x.png index 53fb82a0d..f731b8fcb 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/play@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share.png new file mode 100644 index 000000000..ea0ce74e0 Binary files /dev/null and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share@2x.png new file mode 100644 index 000000000..2566f50c0 Binary files /dev/null and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share@3x.png new file mode 100644 index 000000000..c84cba006 Binary files /dev/null and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/share@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/typing@2x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/typing@2x.png index 1c8788d2f..c451c15b5 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/typing@2x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/typing@2x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/typing@3x.png b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/typing@3x.png index 3849eb128..a5162468f 100644 Binary files a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/typing@3x.png and b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/Images/typing@3x.png differ diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ar.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ar.lproj/JSQMessages.strings new file mode 100644 index 000000000..f064e0538 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ar.lproj/JSQMessages.strings @@ -0,0 +1,29 @@ +// +// Created by Jesse Squires +// http://www.jessesquires.com +// +// +// Documentation +// http://cocoadocs.org/docsets/JSQMessagesViewController +// +// +// GitHub +// https://github.com/jessesquires/JSQMessagesViewController +// +// +// License +// Copyright (c) 2014 Jesse Squires +// Released under an MIT license: http://opensource.org/licenses/MIT +// + +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** + +"load_earlier_messages" = "تحميل الرسائل السابقة"; + +"send" = "أرسال"; + +"new_message" = "رسالة جديدة"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/bs.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/bs.lproj/JSQMessages.strings new file mode 100644 index 000000000..7848b1413 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/bs.lproj/JSQMessages.strings @@ -0,0 +1,37 @@ +// +// 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"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/cs.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/cs.lproj/JSQMessages.strings new file mode 100644 index 000000000..7ca3737e4 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/cs.lproj/JSQMessages.strings @@ -0,0 +1,37 @@ +// +// 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"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/da.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/da.lproj/JSQMessages.strings new file mode 100644 index 000000000..a58416dd3 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/da.lproj/JSQMessages.strings @@ -0,0 +1,37 @@ +// +// 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"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/de.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/de.lproj/JSQMessages.strings index f01533181..8e4ded265 100644 --- a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/de.lproj/JSQMessages.strings +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/de.lproj/JSQMessages.strings @@ -27,3 +27,11 @@ "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"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/en.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/en.lproj/JSQMessages.strings index 53dff590d..0e8cae02d 100644 --- a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/en.lproj/JSQMessages.strings +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/en.lproj/JSQMessages.strings @@ -27,3 +27,11 @@ "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"; \ No newline at end of file diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/es.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/es.lproj/JSQMessages.strings index 7af0e8675..9386bebb7 100644 --- a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/es.lproj/JSQMessages.strings +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/es.lproj/JSQMessages.strings @@ -27,3 +27,11 @@ "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"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fa.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fa.lproj/JSQMessages.strings new file mode 100644 index 000000000..aad11dc00 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fa.lproj/JSQMessages.strings @@ -0,0 +1,19 @@ +/* + 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" = "%@: %@"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fi.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fi.lproj/JSQMessages.strings new file mode 100644 index 000000000..889cffb39 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fi.lproj/JSQMessages.strings @@ -0,0 +1,29 @@ +// +// Created by Jesse Squires +// http://www.jessesquires.com +// +// +// Documentation +// http://cocoadocs.org/docsets/JSQMessagesViewController +// +// +// GitHub +// https://github.com/jessesquires/JSQMessagesViewController +// +// +// License +// Copyright (c) 2014 Jesse Squires +// Released under an MIT license: http://opensource.org/licenses/MIT +// + +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** + +"load_earlier_messages" = "Lataa aiempia viestejä"; + +"send" = "Lähetä"; + +"new_message" = "Uusi viesti"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fr.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fr.lproj/JSQMessages.strings index 8cba48ed6..e0933be15 100644 --- a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fr.lproj/JSQMessages.strings +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/fr.lproj/JSQMessages.strings @@ -24,6 +24,14 @@ "load_earlier_messages" = "Messages précedents"; -"send" = "Envoi"; +"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"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/hr.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/hr.lproj/JSQMessages.strings new file mode 100644 index 000000000..7848b1413 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/hr.lproj/JSQMessages.strings @@ -0,0 +1,37 @@ +// +// 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"; diff --git a/JSQMessagesViewController/Categories/UIDevice+JSQMessages.h b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/id.lproj/JSQMessages.strings similarity index 54% rename from JSQMessagesViewController/Categories/UIDevice+JSQMessages.h rename to JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/id.lproj/JSQMessages.strings index 077f6c0f3..5536d5fb7 100644 --- a/JSQMessagesViewController/Categories/UIDevice+JSQMessages.h +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/id.lproj/JSQMessages.strings @@ -16,13 +16,14 @@ // Released under an MIT license: http://opensource.org/licenses/MIT // -#import +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** -@interface UIDevice (JSQMessages) +"load_earlier_messages" = "Muat Pesan Sebelumnya"; -/** - * @return Whether or not the current device is running a version of iOS before 8.0. - */ -+ (BOOL)jsq_isCurrentDeviceBeforeiOS8; +"send" = "Kirim"; -@end +"new_message" = "Pesan Baru"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ja.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ja.lproj/JSQMessages.strings new file mode 100644 index 000000000..f0073b659 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ja.lproj/JSQMessages.strings @@ -0,0 +1,29 @@ +// +// Created by Jesse Squires +// http://www.jessesquires.com +// +// +// Documentation +// http://cocoadocs.org/docsets/JSQMessagesViewController +// +// +// GitHub +// https://github.com/jessesquires/JSQMessagesViewController +// +// +// License +// Copyright (c) 2014 Jesse Squires +// Released under an MIT license: http://opensource.org/licenses/MIT +// + +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** + +"load_earlier_messages" = "古いメッセージを読み込む"; + +"send" = "送信"; + +"new_message" = "新しいメッセージ"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ko.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ko.lproj/JSQMessages.strings new file mode 100644 index 000000000..5e2727bd4 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ko.lproj/JSQMessages.strings @@ -0,0 +1,29 @@ +// +// Created by Jesse Squires +// http://www.jessesquires.com +// +// +// Documentation +// http://cocoadocs.org/docsets/JSQMessagesViewController +// +// +// GitHub +// https://github.com/jessesquires/JSQMessagesViewController +// +// +// License +// Copyright (c) 2014 Jesse Squires +// Released under an MIT license: http://opensource.org/licenses/MIT +// + +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** + +"load_earlier_messages" = "이전 메시지 불러오기"; + +"send" = "전송"; + +"new_message" = "새로운 메시지"; diff --git a/JSQMessagesViewController/Categories/UIDevice+JSQMessages.m b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ms.lproj/JSQMessages.strings similarity index 54% rename from JSQMessagesViewController/Categories/UIDevice+JSQMessages.m rename to JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ms.lproj/JSQMessages.strings index a490ff564..d1e40847c 100644 --- a/JSQMessagesViewController/Categories/UIDevice+JSQMessages.m +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/ms.lproj/JSQMessages.strings @@ -16,14 +16,14 @@ // Released under an MIT license: http://opensource.org/licenses/MIT // -#import "UIDevice+JSQMessages.h" +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** -@implementation UIDevice (JSQMessages) +"load_earlier_messages" = "Muat Turun Mesej Lama"; -+ (BOOL)jsq_isCurrentDeviceBeforeiOS8 -{ - // iOS < 8.0 - return [[UIDevice currentDevice].systemVersion compare:@"8.0" options:NSNumericSearch] == NSOrderedAscending; -} +"send" = "Hantar"; -@end +"new_message" = "Mesej Baru"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/nb.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/nb.lproj/JSQMessages.strings new file mode 100644 index 000000000..e943a55ad --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/nb.lproj/JSQMessages.strings @@ -0,0 +1,37 @@ +// +// 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"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/pt.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/pt.lproj/JSQMessages.strings index 399410594..d06d434b6 100644 --- a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/pt.lproj/JSQMessages.strings +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/pt.lproj/JSQMessages.strings @@ -22,7 +22,7 @@ // https://github.com/jessesquires/JSQMessagesViewController/issues/237 // ******************************** -"load_earlier_messages" = "Carregar mensagens anteriore"; +"load_earlier_messages" = "Carregar mensagens anteriores"; "send" = "Enviar"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/sv.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/sv.lproj/JSQMessages.strings new file mode 100644 index 000000000..a7948e111 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/sv.lproj/JSQMessages.strings @@ -0,0 +1,29 @@ +// +// Created by Jesse Squires +// http://www.jessesquires.com +// +// +// Documentation +// http://cocoadocs.org/docsets/JSQMessagesViewController +// +// +// GitHub +// https://github.com/jessesquires/JSQMessagesViewController +// +// +// License +// Copyright (c) 2014 Jesse Squires +// Released under an MIT license: http://opensource.org/licenses/MIT +// + +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** + +"load_earlier_messages" = "Visa tidigare meddelanden"; + +"send" = "Skicka"; + +"new_message" = "Nytt meddelande"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/th.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/th.lproj/JSQMessages.strings new file mode 100644 index 000000000..bc9141917 --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/th.lproj/JSQMessages.strings @@ -0,0 +1,29 @@ +// +// Created by Jesse Squires +// http://www.jessesquires.com +// +// +// Documentation +// http://cocoadocs.org/docsets/JSQMessagesViewController +// +// +// GitHub +// https://github.com/jessesquires/JSQMessagesViewController +// +// +// License +// Copyright (c) 2014 Jesse Squires +// Released under an MIT license: http://opensource.org/licenses/MIT +// + +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** + +"load_earlier_messages" = "โหลดข้อความก่อนหน้า"; + +"send" = "ส่ง"; + +"new_message" = "ข้อความใหม่"; diff --git a/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/vi.lproj/JSQMessages.strings b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/vi.lproj/JSQMessages.strings new file mode 100644 index 000000000..f75e6232d --- /dev/null +++ b/JSQMessagesViewController/Assets/JSQMessagesAssets.bundle/vi.lproj/JSQMessages.strings @@ -0,0 +1,29 @@ +// +// Created by Jesse Squires +// http://www.jessesquires.com +// +// +// Documentation +// http://cocoadocs.org/docsets/JSQMessagesViewController +// +// +// GitHub +// https://github.com/jessesquires/JSQMessagesViewController +// +// +// License +// Copyright (c) 2014 Jesse Squires +// Released under an MIT license: http://opensource.org/licenses/MIT +// + +// ******************************** +// Special thanks to the localization contributors! +// +// https://github.com/jessesquires/JSQMessagesViewController/issues/237 +// ******************************** + +"load_earlier_messages" = "Tải thêm tin nhắn"; + +"send" = "Gửi"; + +"new_message" = "Tin nhắn mới"; diff --git a/JSQMessagesViewController/Categories/JSQSystemSoundPlayer+JSQMessages.h b/JSQMessagesViewController/Categories/JSQSystemSoundPlayer+JSQMessages.h deleted file mode 100644 index 0931b1e4d..000000000 --- a/JSQMessagesViewController/Categories/JSQSystemSoundPlayer+JSQMessages.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import - -@interface JSQSystemSoundPlayer (JSQMessages) - -/** - * Plays the default sound for received messages. - */ -+ (void)jsq_playMessageReceivedSound; - -/** - * Plays the default sound for received messages *as an alert*, invoking device vibration if available. - */ -+ (void)jsq_playMessageReceivedAlert; - -/** - * Plays the default sound for sent messages. - */ -+ (void)jsq_playMessageSentSound; - -/** - * Plays the default sound for sent messages *as an alert*, invoking device vibration if available. - */ -+ (void)jsq_playMessageSentAlert; - -@end diff --git a/JSQMessagesViewController/Categories/JSQSystemSoundPlayer+JSQMessages.m b/JSQMessagesViewController/Categories/JSQSystemSoundPlayer+JSQMessages.m deleted file mode 100644 index 7f87e25e3..000000000 --- a/JSQMessagesViewController/Categories/JSQSystemSoundPlayer+JSQMessages.m +++ /dev/null @@ -1,75 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import "JSQSystemSoundPlayer+JSQMessages.h" - -#import "NSBundle+JSQMessages.h" - - -static NSString * const kJSQMessageReceivedSoundName = @"message_received"; -static NSString * const kJSQMessageSentSoundName = @"message_sent"; - - -@implementation JSQSystemSoundPlayer (JSQMessages) - -#pragma mark - Public - -+ (void)jsq_playMessageReceivedSound -{ - [self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageReceivedSoundName asAlert:NO]; -} - -+ (void)jsq_playMessageReceivedAlert -{ - [self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageReceivedSoundName asAlert:YES]; -} - -+ (void)jsq_playMessageSentSound -{ - [self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageSentSoundName asAlert:NO]; -} - -+ (void)jsq_playMessageSentAlert -{ - [self jsq_playSoundFromJSQMessagesBundleWithName:kJSQMessageSentSoundName asAlert:YES]; -} - -#pragma mark - Private - -+ (void)jsq_playSoundFromJSQMessagesBundleWithName:(NSString *)soundName asAlert:(BOOL)asAlert -{ - // save sound player original bundle - NSString *originalPlayerBundleIdentifier = [JSQSystemSoundPlayer sharedPlayer].bundle.bundleIdentifier; - - // search for sounds in this library's bundle - [JSQSystemSoundPlayer sharedPlayer].bundle = [NSBundle jsq_messagesBundle]; - - NSString *fileName = [NSString stringWithFormat:@"JSQMessagesAssets.bundle/Sounds/%@", soundName]; - - if (asAlert) { - [[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithFilename:fileName fileExtension:kJSQSystemSoundTypeAIFF]; - } - else { - [[JSQSystemSoundPlayer sharedPlayer] playSoundWithFilename:fileName fileExtension:kJSQSystemSoundTypeAIFF]; - } - - // restore original bundle - [JSQSystemSoundPlayer sharedPlayer].bundle = [NSBundle bundleWithIdentifier:originalPlayerBundleIdentifier]; -} - -@end diff --git a/JSQMessagesViewController/Categories/NSBundle+JSQMessages.h b/JSQMessagesViewController/Categories/NSBundle+JSQMessages.h index f93be0536..7164663be 100644 --- a/JSQMessagesViewController/Categories/NSBundle+JSQMessages.h +++ b/JSQMessagesViewController/Categories/NSBundle+JSQMessages.h @@ -18,6 +18,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface NSBundle (JSQMessages) /** @@ -37,6 +39,8 @@ * * @return A localized version of the string designated by key in the JSQMessages table. */ -+ (NSString *)jsq_localizedStringForKey:(NSString *)key; ++ (nullable NSString *)jsq_localizedStringForKey:(NSString *)key; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Categories/NSString+JSQMessages.h b/JSQMessagesViewController/Categories/NSString+JSQMessages.h index ba4867202..3e9f0da91 100644 --- a/JSQMessagesViewController/Categories/NSString+JSQMessages.h +++ b/JSQMessagesViewController/Categories/NSString+JSQMessages.h @@ -18,6 +18,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface NSString (JSQMessages) /** @@ -26,3 +28,5 @@ - (NSString *)jsq_stringByTrimingWhitespace; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Categories/UIColor+JSQMessages.h b/JSQMessagesViewController/Categories/UIColor+JSQMessages.h index 0dc579817..53b907e9b 100644 --- a/JSQMessagesViewController/Categories/UIColor+JSQMessages.h +++ b/JSQMessagesViewController/Categories/UIColor+JSQMessages.h @@ -18,6 +18,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface UIColor (JSQMessages) #pragma mark - Message bubble colors @@ -53,4 +55,6 @@ */ - (UIColor *)jsq_colorByDarkeningColorWithValue:(CGFloat)value; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Categories/UIImage+JSQMessages.h b/JSQMessagesViewController/Categories/UIImage+JSQMessages.h index 5b80ad03b..1ea4a8f6f 100644 --- a/JSQMessagesViewController/Categories/UIImage+JSQMessages.h +++ b/JSQMessagesViewController/Categories/UIImage+JSQMessages.h @@ -18,6 +18,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface UIImage (JSQMessages) /** @@ -52,7 +54,7 @@ /** * @return The compact message bubble image. * - * @disscussion This is the default bubble image used by `JSQMessagesBubbleImageFactory`. + * @discussion This is the default bubble image used by `JSQMessagesBubbleImageFactory`. */ + (UIImage *)jsq_bubbleCompactImage; @@ -76,4 +78,18 @@ */ + (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 diff --git a/JSQMessagesViewController/Categories/UIImage+JSQMessages.m b/JSQMessagesViewController/Categories/UIImage+JSQMessages.m index eb99b39b2..da9fa8449 100644 --- a/JSQMessagesViewController/Categories/UIImage+JSQMessages.m +++ b/JSQMessagesViewController/Categories/UIImage+JSQMessages.m @@ -100,4 +100,13 @@ + (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 diff --git a/JSQMessagesViewController/Categories/UIView+JSQMessages.h b/JSQMessagesViewController/Categories/UIView+JSQMessages.h index 2001010ee..e58d94b65 100644 --- a/JSQMessagesViewController/Categories/UIView+JSQMessages.h +++ b/JSQMessagesViewController/Categories/UIView+JSQMessages.h @@ -18,6 +18,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface UIView (JSQMessages) /** @@ -36,3 +38,5 @@ - (void)jsq_pinAllEdgesOfSubview:(UIView *)subview; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.h b/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.h deleted file mode 100644 index 5576e654f..000000000 --- a/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.h +++ /dev/null @@ -1,138 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// -// -// Ideas for keyboard controller taken from Daniel Amitay -// DAKeyboardControl -// https://github.com/danielamitay/DAKeyboardControl -// - -#import -#import - -@class JSQMessagesKeyboardController; - -/** - * Posted when the system keyboard frame changes. - * The object of the notification is the `JSQMessagesKeyboardController` object. - * The `userInfo` dictionary contains the new keyboard frame for key - * `JSQMessagesKeyboardControllerUserInfoKeyKeyboardDidChangeFrame`. - */ -FOUNDATION_EXPORT NSString * const JSQMessagesKeyboardControllerNotificationKeyboardDidChangeFrame; - -/** - * Contains the new keyboard frame wrapped in an `NSValue` object. - */ -FOUNDATION_EXPORT NSString * const JSQMessagesKeyboardControllerUserInfoKeyKeyboardDidChangeFrame; - - -/** - * The `JSQMessagesKeyboardControllerDelegate` protocol defines methods that - * allow you to respond to the frame change events of the system keyboard. - * - * A `JSQMessagesKeyboardController` object also posts the `JSQMessagesKeyboardControllerNotificationKeyboardDidChangeFrame` - * in response to frame change events of the system keyboard. - */ -@protocol JSQMessagesKeyboardControllerDelegate - -@required - -/** - * Tells the delegate that the keyboard frame has changed. - * - * @param keyboardController The keyboard controller that is notifying the delegate. - * @param keyboardFrame The new frame of the keyboard in the coordinate system of the `contextView`. - */ -- (void)keyboardController:(JSQMessagesKeyboardController *)keyboardController keyboardDidChangeFrame:(CGRect)keyboardFrame; - -@end - - -/** - * An instance of `JSQMessagesKeyboardController` manages responding to the hiding and showing - * of the system keyboard for editing its `textView` within its specified `contextView`. - * It also controls user interaction with the system keyboard via its `panGestureRecognizer`, - * allow the user to interactively pan the keyboard up and down in the `contextView`. - * - * When the system keyboard frame changes, it posts the `JSQMessagesKeyboardControllerNotificationKeyboardDidChangeFrame`. - */ -@interface JSQMessagesKeyboardController : NSObject - -/** - * The object that acts as the delegate of the keyboard controller. - */ -@property (weak, nonatomic) id delegate; - -/** - * The text view in which the user is editing with the system keyboard. - */ -@property (weak, nonatomic, readonly) UITextView *textView; - -/** - * The view in which the keyboard will be shown. This should be the parent or a sibling of `textView`. - */ -@property (weak, nonatomic, readonly) UIView *contextView; - -/** - * The pan gesture recognizer responsible for handling user interaction with the system keyboard. - */ -@property (weak, nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer; - -/** - * Specifies the distance from the keyboard at which the `panGestureRecognizer` - * should trigger user interaction with the keyboard by panning. - * - * @discussion The x value of the point is not used. - */ -@property (assign, nonatomic) CGPoint keyboardTriggerPoint; - -/** - * Returns `YES` if the keyboard is currently visible, `NO` otherwise. - */ -@property (assign, nonatomic, readonly) BOOL keyboardIsVisible; - -/** - * Returns the current frame of the keyboard if it is visible, otherwise `CGRectNull`. - */ -@property (assign, nonatomic, readonly) CGRect currentKeyboardFrame; - -/** - * Creates a new keyboard controller object with the specified textView, contextView, panGestureRecognizer, and delegate. - * - * @param textView The text view in which the user is editing with the system keyboard. This value must not be `nil`. - * @param contextView The view in which the keyboard will be shown. This should be the parent or a sibling of `textView`. This value must not be `nil`. - * @param panGestureRecognizer The pan gesture recognizer responsible for handling user interaction with the system keyboard. This value must not be `nil`. - * @param delegate The object that acts as the delegate of the keyboard controller. - * - * @return An initialized `JSQMessagesKeyboardController` if created successfully, `nil` otherwise. - */ -- (instancetype)initWithTextView:(UITextView *)textView - contextView:(UIView *)contextView - panGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer - delegate:(id)delegate; - -/** - * Tells the keyboard controller that it should begin listening for system keyboard notifications. - */ -- (void)beginListeningForKeyboard; - -/** - * Tells the keyboard controller that it should end listening for system keyboard notifications. - */ -- (void)endListeningForKeyboard; - -@end diff --git a/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.m b/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.m deleted file mode 100644 index 5f2fc793c..000000000 --- a/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.m +++ /dev/null @@ -1,398 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// -// -// Ideas for keyboard controller taken from Daniel Amitay -// DAKeyboardControl -// https://github.com/danielamitay/DAKeyboardControl -// - -#import "JSQMessagesKeyboardController.h" - -#import "UIDevice+JSQMessages.h" - - -NSString * const JSQMessagesKeyboardControllerNotificationKeyboardDidChangeFrame = @"JSQMessagesKeyboardControllerNotificationKeyboardDidChangeFrame"; -NSString * const JSQMessagesKeyboardControllerUserInfoKeyKeyboardDidChangeFrame = @"JSQMessagesKeyboardControllerUserInfoKeyKeyboardDidChangeFrame"; - -static void * kJSQMessagesKeyboardControllerKeyValueObservingContext = &kJSQMessagesKeyboardControllerKeyValueObservingContext; - -typedef void (^JSQAnimationCompletionBlock)(BOOL finished); - - - -@interface JSQMessagesKeyboardController () - -@property (assign, nonatomic) BOOL jsq_isObserving; - -@property (weak, nonatomic) UIView *keyboardView; - -- (void)jsq_registerForNotifications; -- (void)jsq_unregisterForNotifications; - -- (void)jsq_didReceiveKeyboardDidShowNotification:(NSNotification *)notification; -- (void)jsq_didReceiveKeyboardWillChangeFrameNotification:(NSNotification *)notification; -- (void)jsq_didReceiveKeyboardDidChangeFrameNotification:(NSNotification *)notification; -- (void)jsq_didReceiveKeyboardDidHideNotification:(NSNotification *)notification; -- (void)jsq_handleKeyboardNotification:(NSNotification *)notification completion:(JSQAnimationCompletionBlock)completion; - -- (void)jsq_setKeyboardViewHidden:(BOOL)hidden; -- (void)jsq_notifyKeyboardFrameNotificationForFrame:(CGRect)frame; -- (void)jsq_resetKeyboardAndTextView; - -- (void)jsq_removeKeyboardFrameObserver; - -- (void)jsq_handlePanGestureRecognizer:(UIPanGestureRecognizer *)pan; - -@end - - - -@implementation JSQMessagesKeyboardController - -#pragma mark - Initialization - -- (instancetype)initWithTextView:(UITextView *)textView - contextView:(UIView *)contextView - panGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer - delegate:(id)delegate - -{ - NSParameterAssert(textView != nil); - NSParameterAssert(contextView != nil); - NSParameterAssert(panGestureRecognizer != nil); - - self = [super init]; - if (self) { - _textView = textView; - _contextView = contextView; - _panGestureRecognizer = panGestureRecognizer; - _delegate = delegate; - _jsq_isObserving = NO; - } - return self; -} - -- (void)dealloc -{ - [self jsq_removeKeyboardFrameObserver]; - [self jsq_unregisterForNotifications]; - _textView = nil; - _contextView = nil; - _panGestureRecognizer = nil; - _delegate = nil; - _keyboardView = nil; -} - -#pragma mark - Setters - -- (void)setKeyboardView:(UIView *)keyboardView -{ - if (_keyboardView) { - [self jsq_removeKeyboardFrameObserver]; - } - - _keyboardView = keyboardView; - - if (keyboardView && !_jsq_isObserving) { - [_keyboardView addObserver:self - forKeyPath:NSStringFromSelector(@selector(frame)) - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:kJSQMessagesKeyboardControllerKeyValueObservingContext]; - - _jsq_isObserving = YES; - } -} - -#pragma mark - Getters - -- (BOOL)keyboardIsVisible -{ - return self.keyboardView != nil; -} - -- (CGRect)currentKeyboardFrame -{ - if (!self.keyboardIsVisible) { - return CGRectNull; - } - - return self.keyboardView.frame; -} - -#pragma mark - Keyboard controller - -- (void)beginListeningForKeyboard -{ - if (self.textView.inputAccessoryView == nil) { - self.textView.inputAccessoryView = [[UIView alloc] init]; - } - - [self jsq_registerForNotifications]; -} - -- (void)endListeningForKeyboard -{ - [self jsq_unregisterForNotifications]; - - [self jsq_setKeyboardViewHidden:NO]; - self.keyboardView = nil; -} - -#pragma mark - Notifications - -- (void)jsq_registerForNotifications -{ - [self jsq_unregisterForNotifications]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(jsq_didReceiveKeyboardDidShowNotification:) - name:UIKeyboardDidShowNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(jsq_didReceiveKeyboardWillChangeFrameNotification:) - name:UIKeyboardWillChangeFrameNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(jsq_didReceiveKeyboardDidChangeFrameNotification:) - name:UIKeyboardDidChangeFrameNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(jsq_didReceiveKeyboardDidHideNotification:) - name:UIKeyboardDidHideNotification - object:nil]; -} - -- (void)jsq_unregisterForNotifications -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)jsq_didReceiveKeyboardDidShowNotification:(NSNotification *)notification -{ - self.keyboardView = self.textView.inputAccessoryView.superview; - [self jsq_setKeyboardViewHidden:NO]; - - [self jsq_handleKeyboardNotification:notification completion:^(BOOL finished) { - [self.panGestureRecognizer addTarget:self action:@selector(jsq_handlePanGestureRecognizer:)]; - }]; -} - -- (void)jsq_didReceiveKeyboardWillChangeFrameNotification:(NSNotification *)notification -{ - [self jsq_handleKeyboardNotification:notification completion:nil]; -} - -- (void)jsq_didReceiveKeyboardDidChangeFrameNotification:(NSNotification *)notification -{ - [self jsq_setKeyboardViewHidden:NO]; - - [self jsq_handleKeyboardNotification:notification completion:nil]; -} - -- (void)jsq_didReceiveKeyboardDidHideNotification:(NSNotification *)notification -{ - self.keyboardView = nil; - - [self jsq_handleKeyboardNotification:notification completion:^(BOOL finished) { - [self.panGestureRecognizer removeTarget:self action:NULL]; - }]; -} - -- (void)jsq_handleKeyboardNotification:(NSNotification *)notification completion:(JSQAnimationCompletionBlock)completion -{ - NSDictionary *userInfo = [notification userInfo]; - - CGRect keyboardEndFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - - if (CGRectIsNull(keyboardEndFrame)) { - return; - } - - UIViewAnimationCurve animationCurve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; - NSInteger animationCurveOption = (animationCurve << 16); - - double animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - - CGRect keyboardEndFrameConverted = [self.contextView convertRect:keyboardEndFrame fromView:nil]; - - [UIView animateWithDuration:animationDuration - delay:0.0 - options:animationCurveOption - animations:^{ - [self jsq_notifyKeyboardFrameNotificationForFrame:keyboardEndFrameConverted]; - } - completion:^(BOOL finished) { - if (completion) { - completion(finished); - } - }]; -} - -#pragma mark - Utilities - -- (void)jsq_setKeyboardViewHidden:(BOOL)hidden -{ - self.keyboardView.hidden = hidden; - self.keyboardView.userInteractionEnabled = !hidden; -} - -- (void)jsq_notifyKeyboardFrameNotificationForFrame:(CGRect)frame -{ - [self.delegate keyboardController:self keyboardDidChangeFrame:frame]; - - [[NSNotificationCenter defaultCenter] postNotificationName:JSQMessagesKeyboardControllerNotificationKeyboardDidChangeFrame - object:self - userInfo:@{ JSQMessagesKeyboardControllerUserInfoKeyKeyboardDidChangeFrame : [NSValue valueWithCGRect:frame] }]; -} - -- (void)jsq_resetKeyboardAndTextView -{ - [self jsq_setKeyboardViewHidden:YES]; - [self jsq_removeKeyboardFrameObserver]; - [self.textView resignFirstResponder]; -} - -#pragma mark - Key-value observing - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if (context == kJSQMessagesKeyboardControllerKeyValueObservingContext) { - - if (object == self.keyboardView && [keyPath isEqualToString:NSStringFromSelector(@selector(frame))]) { - - CGRect oldKeyboardFrame = [[change objectForKey:NSKeyValueChangeOldKey] CGRectValue]; - CGRect newKeyboardFrame = [[change objectForKey:NSKeyValueChangeNewKey] CGRectValue]; - - if (CGRectEqualToRect(newKeyboardFrame, oldKeyboardFrame) || CGRectIsNull(newKeyboardFrame)) { - return; - } - - CGRect keyboardEndFrameConverted = [self.contextView convertRect:newKeyboardFrame - fromView:self.keyboardView.superview]; - [self jsq_notifyKeyboardFrameNotificationForFrame:keyboardEndFrameConverted]; - } - } -} - -- (void)jsq_removeKeyboardFrameObserver -{ - if (!_jsq_isObserving) { - return; - } - - @try { - [_keyboardView removeObserver:self - forKeyPath:NSStringFromSelector(@selector(frame)) - context:kJSQMessagesKeyboardControllerKeyValueObservingContext]; - } - @catch (NSException * __unused exception) { } - - _jsq_isObserving = NO; -} - -#pragma mark - Pan gesture recognizer - -- (void)jsq_handlePanGestureRecognizer:(UIPanGestureRecognizer *)pan -{ - CGPoint touch = [pan locationInView:self.contextView]; - - // system keyboard is added to a new UIWindow, need to operate in window coordinates - // also, keyboard always slides from bottom of screen, not the bottom of a view - CGFloat contextViewWindowHeight = CGRectGetHeight(self.contextView.window.frame); - - if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) { - // handle iOS 7 bug when rotating to landscape - if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { - contextViewWindowHeight = CGRectGetWidth(self.contextView.window.frame); - } - } - - CGFloat keyboardViewHeight = CGRectGetHeight(self.keyboardView.frame); - - CGFloat dragThresholdY = (contextViewWindowHeight - keyboardViewHeight - self.keyboardTriggerPoint.y); - - CGRect newKeyboardViewFrame = self.keyboardView.frame; - - BOOL userIsDraggingNearThresholdForDismissing = (touch.y > dragThresholdY); - - self.keyboardView.userInteractionEnabled = !userIsDraggingNearThresholdForDismissing; - - switch (pan.state) { - case UIGestureRecognizerStateChanged: - { - newKeyboardViewFrame.origin.y = touch.y + self.keyboardTriggerPoint.y; - - // bound frame between bottom of view and height of keyboard - newKeyboardViewFrame.origin.y = MIN(newKeyboardViewFrame.origin.y, contextViewWindowHeight); - newKeyboardViewFrame.origin.y = MAX(newKeyboardViewFrame.origin.y, contextViewWindowHeight - keyboardViewHeight); - - if (CGRectGetMinY(newKeyboardViewFrame) == CGRectGetMinY(self.keyboardView.frame)) { - return; - } - - [UIView animateWithDuration:0.0 - delay:0.0 - options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionTransitionNone - animations:^{ - self.keyboardView.frame = newKeyboardViewFrame; - } - completion:nil]; - } - break; - - case UIGestureRecognizerStateEnded: - case UIGestureRecognizerStateCancelled: - case UIGestureRecognizerStateFailed: - { - BOOL keyboardViewIsHidden = (CGRectGetMinY(self.keyboardView.frame) >= contextViewWindowHeight); - if (keyboardViewIsHidden) { - [self jsq_resetKeyboardAndTextView]; - return; - } - - CGPoint velocity = [pan velocityInView:self.contextView]; - BOOL userIsScrollingDown = (velocity.y > 0.0f); - BOOL shouldHide = (userIsScrollingDown && userIsDraggingNearThresholdForDismissing); - - newKeyboardViewFrame.origin.y = shouldHide ? contextViewWindowHeight : (contextViewWindowHeight - keyboardViewHeight); - - [UIView animateWithDuration:0.25 - delay:0.0 - options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseOut - animations:^{ - self.keyboardView.frame = newKeyboardViewFrame; - } - completion:^(BOOL finished) { - self.keyboardView.userInteractionEnabled = !shouldHide; - - if (shouldHide) { - [self jsq_resetKeyboardAndTextView]; - } - }]; - } - break; - - default: - break; - } -} - -@end diff --git a/JSQMessagesViewController/Controllers/JSQMessagesViewController.h b/JSQMessagesViewController/Controllers/JSQMessagesViewController.h index 6f25f42a7..4a0d52d40 100644 --- a/JSQMessagesViewController/Controllers/JSQMessagesViewController.h +++ b/JSQMessagesViewController/Controllers/JSQMessagesViewController.h @@ -21,7 +21,8 @@ #import "JSQMessagesCollectionView.h" #import "JSQMessagesCollectionViewFlowLayout.h" #import "JSQMessagesInputToolbar.h" -#import "JSQMessagesKeyboardController.h" + +NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesViewController` class is an abstract class that represents a view controller whose content consists of @@ -34,43 +35,22 @@ UITextViewDelegate> /** - * Returns the collection view object managed by this view controller. + * Returns the collection view object managed by this view controller. * This view controller is the collection view's data source and delegate. */ -@property (weak, nonatomic, readonly) JSQMessagesCollectionView *collectionView; +@property (weak, nonatomic, readonly, nullable) JSQMessagesCollectionView *collectionView; /** - * Returns the input toolbar view object managed by this view controller. + * Returns the input toolbar view object managed by this view controller. * This view controller is the toolbar's delegate. */ -@property (weak, nonatomic, readonly) JSQMessagesInputToolbar *inputToolbar; - -/** - * Returns the keyboard controller object used to manage the software keyboard. - */ -@property (strong, nonatomic) JSQMessagesKeyboardController *keyboardController; - -/** - * The display name of the current user who is sending messages. - * - * @discussion This value does not have to be unique. This value must not be `nil`. - */ -@property (copy, nonatomic) NSString *senderDisplayName; - -/** - * The string identifier that uniquely identifies the current user sending messages. - * - * @discussion This property is used to determine if a message is incoming or outgoing. - * All message data objects returned by `collectionView:messageDataForItemAtIndexPath:` are - * checked against this identifier. This value must not be `nil`. - */ -@property (copy, nonatomic) NSString *senderId; +@property (strong, nonatomic, readonly) JSQMessagesInputToolbar *inputToolbar; /** - * Specifies whether or not the view controller should automatically scroll to the most recent message + * Specifies whether or not the view controller should automatically scroll to the most recent message * when the view appears and when sending, receiving, and composing a new message. * - * @discussion The default value is `YES`, which allows the view controller to scroll automatically to the most recent message. + * @discussion The default value is `YES`, which allows the view controller to scroll automatically to the most recent message. * Set to `NO` if you want to manage scrolling yourself. */ @property (assign, nonatomic) BOOL automaticallyScrollsToMostRecentMessage; @@ -82,7 +62,7 @@ * @discussion This cell identifier is used for outgoing text message data items. * The default value is the string returned by `[JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]`. * This value must not be `nil`. - * + * * @see JSQMessagesCollectionViewCellOutgoing. * * @warning Overriding this property's default value is *not* recommended. @@ -94,7 +74,7 @@ @property (copy, nonatomic) NSString *outgoingCellIdentifier; /** - * The collection view cell identifier to use for dequeuing outgoing message collection view cells + * The collection view cell identifier to use for dequeuing outgoing message collection view cells * in the collectionView for media messages. * * @discussion This cell identifier is used for outgoing media message data items. @@ -166,19 +146,19 @@ @property (assign, nonatomic) BOOL showLoadEarlierMessagesHeader; /** - * Specifies an additional inset amount to be added to the collectionView's contentInsets.top value. + * 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 top content inset to account for a custom subview at the top of your view controller. + * @discussion Use this property to adjust the insets to account for a custom subview in your view controller. */ -@property (assign, nonatomic) CGFloat topContentAdditionalInset; +@property (assign, nonatomic) UIEdgeInsets additionalContentInset; #pragma mark - Class methods /** * Returns the `UINib` object initialized for a `JSQMessagesViewController`. * - * @return The initialized `UINib` object or `nil` if there were errors during initialization - * or the nib file could not be located. + * @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 @@ -188,10 +168,10 @@ /** * Creates and returns a new `JSQMessagesViewController` object. - * + * * @discussion This is the designated initializer for programmatic instantiation. * - * @return An initialized `JSQMessagesViewController` object if successful, `nil` otherwise. + * @return An initialized `JSQMessagesViewController` object. */ + (instancetype)messagesViewController; @@ -268,4 +248,62 @@ */ - (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 diff --git a/JSQMessagesViewController/Controllers/JSQMessagesViewController.m b/JSQMessagesViewController/Controllers/JSQMessagesViewController.m index d9642c14a..cdf40c5b0 100644 --- a/JSQMessagesViewController/Controllers/JSQMessagesViewController.m +++ b/JSQMessagesViewController/Controllers/JSQMessagesViewController.m @@ -30,71 +30,96 @@ #import "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesLoadEarlierHeaderView.h" -#import "JSQMessagesToolbarContentView.h" -#import "JSQMessagesInputToolbar.h" -#import "JSQMessagesComposerTextView.h" - -#import "JSQMessagesTimestampFormatter.h" - #import "NSString+JSQMessages.h" -#import "UIColor+JSQMessages.h" -#import "UIDevice+JSQMessages.h" #import "NSBundle+JSQMessages.h" +#import -static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObservingContext; - - - -@interface JSQMessagesViewController () - -@property (weak, nonatomic) IBOutlet JSQMessagesCollectionView *collectionView; -@property (weak, nonatomic) IBOutlet JSQMessagesInputToolbar *inputToolbar; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *toolbarHeightConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *toolbarBottomLayoutGuide; - -@property (weak, nonatomic) UIView *snapshotView; -@property (assign, nonatomic) BOOL jsq_isObserving; - -@property (strong, nonatomic) NSIndexPath *selectedIndexPathForMenu; +// 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); -- (void)jsq_configureMessagesViewController; + // get original method + Method origMethod = class_getInstanceMethod(c, origSEL); + NSCParameterAssert(origMethod); -- (NSString *)jsq_currentlyComposedMessageText; + // convert block to IMP trampoline and replace method implementation + IMP newIMP = imp_implementationWithBlock(block); -- (void)jsq_handleDidChangeStatusBarFrameNotification:(NSNotification *)notification; -- (void)jsq_didReceiveMenuWillShowNotification:(NSNotification *)notification; -- (void)jsq_didReceiveMenuWillHideNotification:(NSNotification *)notification; + // 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); + } +} -- (void)jsq_updateKeyboardTriggerPoint; -- (void)jsq_setToolbarBottomLayoutGuideConstant:(CGFloat)constant; +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(); + }); + }; -- (void)jsq_handleInteractivePopGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer; + // _UIRotatingAlertController + Class alertClass = NSClassFromString([NSString stringWithFormat:@"%@%@%@", @"_U", @"IRotat", @"ingAlertController"]); + if (alertClass) { + swizzleOnClass(alertClass); + } -- (BOOL)jsq_inputToolbarHasReachedMaximumHeight; -- (void)jsq_adjustInputToolbarForComposerTextViewContentSizeChange:(CGFloat)dy; -- (void)jsq_adjustInputToolbarHeightConstraintByDelta:(CGFloat)dy; -- (void)jsq_scrollComposerTextViewToBottomAnimated:(BOOL)animated; + // WKActionSheet + Class actionSheetClass = NSClassFromString([NSString stringWithFormat:@"%@%@%@", @"W", @"KActio", @"nSheet"]); + if (actionSheetClass) { + swizzleOnClass(actionSheetClass); + } +} -- (void)jsq_updateCollectionViewInsets; -- (void)jsq_setCollectionViewInsetsTopValue:(CGFloat)top bottomValue:(CGFloat)bottom; -- (BOOL)jsq_isMenuVisible; +@interface JSQMessagesViewController () -- (void)jsq_addObservers; -- (void)jsq_removeObservers; +@property (weak, nonatomic) IBOutlet JSQMessagesCollectionView *collectionView; +@property (strong, nonatomic) IBOutlet JSQMessagesInputToolbar *inputToolbar; -- (void)jsq_registerForNotifications:(BOOL)registerForNotifications; +@property (nonatomic) NSLayoutConstraint *toolbarHeightConstraint; -- (void)jsq_addActionToInteractivePopGestureRecognizer:(BOOL)addAction; +@property (strong, nonatomic) NSIndexPath *selectedIndexPathForMenu; @end - @implementation JSQMessagesViewController #pragma mark - Class methods @@ -111,14 +136,19 @@ + (instancetype)messagesViewController 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.jsq_isObserving = NO; - self.toolbarHeightConstraint.constant = self.inputToolbar.preferredDefaultHeight; self.collectionView.dataSource = self; @@ -126,7 +156,10 @@ - (void)jsq_configureMessagesViewController 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; @@ -136,43 +169,27 @@ - (void)jsq_configureMessagesViewController 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.topContentAdditionalInset = 0.0f; + self.additionalContentInset = UIEdgeInsetsZero; [self jsq_updateCollectionViewInsets]; - - self.keyboardController = [[JSQMessagesKeyboardController alloc] initWithTextView:self.inputToolbar.contentView.textView - contextView:self.view - panGestureRecognizer:self.collectionView.panGestureRecognizer - delegate:self]; } - (void)dealloc { [self jsq_registerForNotifications:NO]; - [self jsq_removeObservers]; _collectionView.dataSource = nil; _collectionView.delegate = nil; - _collectionView = nil; - + _inputToolbar.contentView.textView.delegate = nil; _inputToolbar.delegate = nil; - _inputToolbar = nil; - - _toolbarHeightConstraint = nil; - _toolbarBottomLayoutGuide = nil; - - _senderId = nil; - _senderDisplayName = nil; - _outgoingCellIdentifier = nil; - _incomingCellIdentifier = nil; - - [_keyboardController endListeningForKeyboard]; - _keyboardController = nil; } #pragma mark - Setters @@ -200,9 +217,9 @@ - (void)setShowLoadEarlierMessagesHeader:(BOOL)showLoadEarlierMessagesHeader [self.collectionView reloadData]; } -- (void)setTopContentAdditionalInset:(CGFloat)topContentAdditionalInset +- (void)setAdditionalContentInset:(UIEdgeInsets)additionalContentInset { - _topContentAdditionalInset = topContentAdditionalInset; + _additionalContentInset = additionalContentInset; [self jsq_updateCollectionViewInsets]; } @@ -220,10 +237,10 @@ - (void)viewDidLoad - (void)viewWillAppear:(BOOL)animated { - NSParameterAssert(self.senderId != nil); - NSParameterAssert(self.senderDisplayName != nil); - [super viewWillAppear:animated]; + if (!self.inputToolbar.contentView.textView.hasText) { + self.toolbarHeightConstraint.constant = self.inputToolbar.preferredDefaultHeight; + } [self.view layoutIfNeeded]; [self.collectionView.collectionViewLayout invalidateLayout]; @@ -233,40 +250,22 @@ - (void)viewWillAppear:(BOOL)animated [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; }); } - - [self jsq_updateKeyboardTriggerPoint]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - [self jsq_addObservers]; - [self jsq_addActionToInteractivePopGestureRecognizer:YES]; - [self.keyboardController beginListeningForKeyboard]; - - if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) { - [self.snapshotView removeFromSuperview]; - } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - [self jsq_addActionToInteractivePopGestureRecognizer:NO]; self.collectionView.collectionViewLayout.springinessEnabled = NO; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; - [self jsq_removeObservers]; - [self.keyboardController endListeningForKeyboard]; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - NSLog(@"MEMORY WARNING: %s", __PRETTY_FUNCTION__); } #pragma mark - View rotation @@ -276,7 +275,7 @@ - (BOOL)shouldAutorotate return YES; } -- (NSUInteger)supportedInterfaceOrientations +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) { return UIInterfaceOrientationMaskAllButUpsideDown; @@ -300,6 +299,23 @@ - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceO } } +- (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 @@ -327,8 +343,6 @@ - (void)finishSendingMessageAnimated:(BOOL)animated { textView.text = nil; [textView.undoManager removeAllActions]; - [self.inputToolbar toggleSendButtonEnabled]; - [[NSNotificationCenter defaultCenter] postNotificationName:UITextViewTextDidChangeNotification object:textView]; [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; @@ -354,6 +368,8 @@ - (void)finishReceivingMessageAnimated:(BOOL)animated { if (self.automaticallyScrollsToMostRecentMessage && ![self jsq_isMenuVisible]) { [self scrollToBottomAnimated:animated]; } + + UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, [NSBundle jsq_localizedStringForKey:@"new_message_received_accessibility_announcement"]); } - (void)scrollToBottomAnimated:(BOOL)animated @@ -362,9 +378,19 @@ - (void)scrollToBottomAnimated:(BOOL)animated return; } - NSInteger items = [self.collectionView numberOfItemsInSection:0]; + 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; + } - if (items == 0) { + NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:indexPath.section]; + if (numberOfItems == 0) { return; } @@ -380,30 +406,57 @@ - (void)scrollToBottomAnimated:(BOOL)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 - NSUInteger finalRow = MAX(0, [self.collectionView numberOfItemsInSection:0] - 1); - NSIndexPath *finalIndexPath = [NSIndexPath indexPathForItem:finalRow inSection:0]; - CGSize finalCellSize = [self.collectionView.collectionViewLayout sizeForItemAtIndexPath:finalIndexPath]; - - CGFloat maxHeightForVisibleMessage = CGRectGetHeight(self.collectionView.bounds) - self.collectionView.contentInset.top - CGRectGetHeight(self.inputToolbar.bounds); - - UICollectionViewScrollPosition scrollPosition = (finalCellSize.height > maxHeightForVisibleMessage) ? UICollectionViewScrollPositionBottom : UICollectionViewScrollPositionTop; - - [self.collectionView scrollToItemAtIndexPath:finalIndexPath + 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__); @@ -448,10 +501,7 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection id messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath]; NSParameterAssert(messageItem != nil); - NSString *messageSenderId = [messageItem senderId]; - NSParameterAssert(messageSenderId != nil); - - BOOL isOutgoingMessage = [messageSenderId isEqualToString:self.senderId]; + BOOL isOutgoingMessage = [self isOutgoingMessage:messageItem]; BOOL isMediaMessage = [messageItem isMediaMessage]; NSString *cellIdentifier = nil; @@ -463,25 +513,16 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection } 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]; - - if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) { - // workaround for iOS 7 textView data detectors bug - cell.textView.text = nil; - cell.textView.attributedText = [[NSAttributedString alloc] initWithString:[messageItem text] - attributes:@{ NSFontAttributeName : collectionView.collectionViewLayout.messageBubbleFont }]; - } - NSParameterAssert(cell.textView.text != nil); id bubbleImageDataSource = [collectionView.dataSource collectionView:collectionView messageBubbleImageDataForItemAtIndexPath:indexPath]; - if (bubbleImageDataSource != nil) { - cell.messageBubbleImageView.image = [bubbleImageDataSource messageBubbleImage]; - cell.messageBubbleImageView.highlightedImage = [bubbleImageDataSource messageBubbleHighlightedImage]; - } + cell.messageBubbleImageView.image = [bubbleImageDataSource messageBubbleImage]; + cell.messageBubbleImageView.highlightedImage = [bubbleImageDataSource messageBubbleHighlightedImage]; } else { id messageMedia = [messageItem media]; @@ -532,10 +573,29 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection 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 @@ -577,6 +637,10 @@ - (BOOL)collectionView:(JSQMessagesCollectionView *)collectionView shouldShowMen // 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; } @@ -588,13 +652,20 @@ - (BOOL)collectionView:(JSQMessagesCollectionView *)collectionView shouldShowMen // 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:)) { + if (action == @selector(copy:) || action == @selector(delete:)) { return YES; } @@ -604,8 +675,24 @@ - (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL) - (void)collectionView:(JSQMessagesCollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender { if (action == @selector(copy:)) { - id messageData = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath]; - [[UIPasteboard generalPasteboard] setString:[messageData text]]; + + 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]; } } @@ -649,25 +736,25 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView - (void)messagesInputToolbar:(JSQMessagesInputToolbar *)toolbar didPressLeftBarButton:(UIButton *)sender { - if (toolbar.sendButtonOnRight) { - [self didPressAccessoryButton:sender]; - } - else { + if (toolbar.sendButtonLocation == JSQMessagesInputSendButtonLocationLeft) { [self didPressSendButton:sender withMessageText:[self jsq_currentlyComposedMessageText] - senderId:self.senderId - senderDisplayName:self.senderDisplayName + 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.sendButtonOnRight) { + if (toolbar.sendButtonLocation == JSQMessagesInputSendButtonLocationRight) { [self didPressSendButton:sender withMessageText:[self jsq_currentlyComposedMessageText] - senderId:self.senderId - senderDisplayName:self.senderDisplayName + senderId:[self.collectionView.dataSource senderId] + senderDisplayName:[self.collectionView.dataSource senderDisplayName] date:[NSDate date]]; } else { @@ -684,6 +771,18 @@ - (NSString *)jsq_currentlyComposedMessageText 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 @@ -704,8 +803,6 @@ - (void)textViewDidChange:(UITextView *)textView if (textView != self.inputToolbar.contentView.textView) { return; } - - [self.inputToolbar toggleSendButtonEnabled]; } - (void)textViewDidEndEditing:(UITextView *)textView @@ -719,14 +816,7 @@ - (void)textViewDidEndEditing:(UITextView *)textView #pragma mark - Notifications -- (void)jsq_handleDidChangeStatusBarFrameNotification:(NSNotification *)notification -{ - if (self.keyboardController.keyboardIsVisible) { - [self jsq_setToolbarBottomLayoutGuideConstant:CGRectGetHeight(self.keyboardController.currentKeyboardFrame)]; - } -} - -- (void)jsq_didReceiveMenuWillShowNotification:(NSNotification *)notification +- (void)didReceiveMenuWillShowNotification:(NSNotification *)notification { if (!self.selectedIndexPathForMenu) { return; @@ -746,12 +836,12 @@ - (void)jsq_didReceiveMenuWillShowNotification:(NSNotification *)notification [menu setMenuVisible:YES animated:YES]; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(jsq_didReceiveMenuWillShowNotification:) + selector:@selector(didReceiveMenuWillShowNotification:) name:UIMenuControllerWillShowMenuNotification object:nil]; } -- (void)jsq_didReceiveMenuWillHideNotification:(NSNotification *)notification +- (void)didReceiveMenuWillHideNotification:(NSNotification *)notification { if (!self.selectedIndexPathForMenu) { return; @@ -764,180 +854,24 @@ - (void)jsq_didReceiveMenuWillHideNotification:(NSNotification *)notification self.selectedIndexPathForMenu = nil; } -#pragma mark - Key-value observing - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if (context == kJSQMessagesKeyValueObservingContext) { - - if (object == self.inputToolbar.contentView.textView - && [keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) { - - CGSize oldContentSize = [[change objectForKey:NSKeyValueChangeOldKey] CGSizeValue]; - CGSize newContentSize = [[change objectForKey:NSKeyValueChangeNewKey] CGSizeValue]; - - CGFloat dy = newContentSize.height - oldContentSize.height; - - [self jsq_adjustInputToolbarForComposerTextViewContentSizeChange:dy]; - [self jsq_updateCollectionViewInsets]; - if (self.automaticallyScrollsToMostRecentMessage) { - [self scrollToBottomAnimated:NO]; - } - } - } -} - -#pragma mark - Keyboard controller delegate - -- (void)keyboardController:(JSQMessagesKeyboardController *)keyboardController keyboardDidChangeFrame:(CGRect)keyboardFrame -{ - if (![self.inputToolbar.contentView.textView isFirstResponder] && self.toolbarBottomLayoutGuide.constant == 0.0f) { - return; - } - - CGFloat heightFromBottom = CGRectGetMaxY(self.collectionView.frame) - CGRectGetMinY(keyboardFrame); - - heightFromBottom = MAX(0.0f, heightFromBottom); - - [self jsq_setToolbarBottomLayoutGuideConstant:heightFromBottom]; -} - -- (void)jsq_setToolbarBottomLayoutGuideConstant:(CGFloat)constant -{ - self.toolbarBottomLayoutGuide.constant = constant; - [self.view setNeedsUpdateConstraints]; - [self.view layoutIfNeeded]; - - [self jsq_updateCollectionViewInsets]; -} - -- (void)jsq_updateKeyboardTriggerPoint -{ - self.keyboardController.keyboardTriggerPoint = CGPointMake(0.0f, CGRectGetHeight(self.inputToolbar.bounds)); -} - -#pragma mark - Gesture recognizers - -- (void)jsq_handleInteractivePopGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer -{ - switch (gestureRecognizer.state) { - case UIGestureRecognizerStateBegan: - { - if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) { - [self.snapshotView removeFromSuperview]; - } - - [self.keyboardController endListeningForKeyboard]; - - if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) { - [self.inputToolbar.contentView.textView resignFirstResponder]; - [UIView animateWithDuration:0.0 - animations:^{ - [self jsq_setToolbarBottomLayoutGuideConstant:0.0f]; - }]; - - UIView *snapshot = [self.view snapshotViewAfterScreenUpdates:YES]; - [self.view addSubview:snapshot]; - self.snapshotView = snapshot; - } - } - break; - case UIGestureRecognizerStateChanged: - break; - case UIGestureRecognizerStateCancelled: - case UIGestureRecognizerStateEnded: - case UIGestureRecognizerStateFailed: - [self.keyboardController beginListeningForKeyboard]; - - if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) { - [self.snapshotView removeFromSuperview]; - } - break; - default: - break; - } -} - -#pragma mark - Input toolbar utilities - -- (BOOL)jsq_inputToolbarHasReachedMaximumHeight -{ - return CGRectGetMinY(self.inputToolbar.frame) == (self.topLayoutGuide.length + self.topContentAdditionalInset); -} - -- (void)jsq_adjustInputToolbarForComposerTextViewContentSizeChange:(CGFloat)dy -{ - BOOL contentSizeIsIncreasing = (dy > 0); - - if ([self jsq_inputToolbarHasReachedMaximumHeight]) { - BOOL contentOffsetIsPositive = (self.inputToolbar.contentView.textView.contentOffset.y > 0); - - if (contentSizeIsIncreasing || contentOffsetIsPositive) { - [self jsq_scrollComposerTextViewToBottomAnimated:YES]; - return; - } - } - - CGFloat toolbarOriginY = CGRectGetMinY(self.inputToolbar.frame); - CGFloat newToolbarOriginY = toolbarOriginY - dy; - - // attempted to increase origin.Y above topLayoutGuide - if (newToolbarOriginY <= self.topLayoutGuide.length + self.topContentAdditionalInset) { - dy = toolbarOriginY - (self.topLayoutGuide.length + self.topContentAdditionalInset); - [self jsq_scrollComposerTextViewToBottomAnimated:YES]; - } - - [self jsq_adjustInputToolbarHeightConstraintByDelta:dy]; - - [self jsq_updateKeyboardTriggerPoint]; - - if (dy < 0) { - [self jsq_scrollComposerTextViewToBottomAnimated:NO]; - } -} - -- (void)jsq_adjustInputToolbarHeightConstraintByDelta:(CGFloat)dy -{ - self.toolbarHeightConstraint.constant += dy; - - if (self.toolbarHeightConstraint.constant < self.inputToolbar.preferredDefaultHeight) { - self.toolbarHeightConstraint.constant = self.inputToolbar.preferredDefaultHeight; - } - - [self.view setNeedsUpdateConstraints]; - [self.view layoutIfNeeded]; -} - -- (void)jsq_scrollComposerTextViewToBottomAnimated:(BOOL)animated +- (void)preferredContentSizeChanged:(NSNotification *)notification { - UITextView *textView = self.inputToolbar.contentView.textView; - CGPoint contentOffsetToShowLastLine = CGPointMake(0.0f, textView.contentSize.height - CGRectGetHeight(textView.bounds)); - - if (!animated) { - textView.contentOffset = contentOffsetToShowLastLine; - return; - } - - [UIView animateWithDuration:0.01 - delay:0.01 - options:UIViewAnimationOptionCurveLinear - animations:^{ - textView.contentOffset = contentOffsetToShowLastLine; - } - completion:nil]; + [self.collectionView.collectionViewLayout invalidateLayout]; + [self.collectionView setNeedsLayout]; } #pragma mark - Collection view utilities - (void)jsq_updateCollectionViewInsets { - [self jsq_setCollectionViewInsetsTopValue:self.topLayoutGuide.length + self.topContentAdditionalInset - bottomValue:CGRectGetMaxY(self.collectionView.frame) - CGRectGetMinY(self.inputToolbar.frame)]; + 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(top, 0.0f, bottom, 0.0f); + UIEdgeInsets insets = UIEdgeInsetsMake(self.topLayoutGuide.length + top, 0.0f, bottom, 0.0f); self.collectionView.contentInset = insets; self.collectionView.scrollIndicatorInsets = insets; } @@ -951,80 +885,73 @@ - (BOOL)jsq_isMenuVisible #pragma mark - Utilities -- (void)jsq_addObservers +- (void)jsq_registerForNotifications:(BOOL)registerForNotifications { - if (self.jsq_isObserving) { - return; + 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]; - [self.inputToolbar.contentView.textView addObserver:self - forKeyPath:NSStringFromSelector(@selector(contentSize)) - options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew - context:kJSQMessagesKeyValueObservingContext]; - - self.jsq_isObserving = YES; -} + [center removeObserver:self + name:UIMenuControllerWillShowMenuNotification + object:nil]; -- (void)jsq_removeObservers -{ - if (!_jsq_isObserving) { - return; - } + [center removeObserver:self + name:UIMenuControllerWillHideMenuNotification + object:nil]; - @try { - [_inputToolbar.contentView.textView removeObserver:self - forKeyPath:NSStringFromSelector(@selector(contentSize)) - context:kJSQMessagesKeyValueObservingContext]; + [center removeObserver:self + name:UIContentSizeCategoryDidChangeNotification + object:nil]; } - @catch (NSException * __unused exception) { } - - _jsq_isObserving = NO; } -- (void)jsq_registerForNotifications:(BOOL)registerForNotifications +- (void)jsq_didReceiveKeyboardWillChangeFrameNotification:(NSNotification *)notification { - if (registerForNotifications) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(jsq_handleDidChangeStatusBarFrameNotification:) - name:UIApplicationDidChangeStatusBarFrameNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(jsq_didReceiveMenuWillShowNotification:) - name:UIMenuControllerWillShowMenuNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(jsq_didReceiveMenuWillHideNotification:) - name:UIMenuControllerWillHideMenuNotification - object:nil]; - } - else { - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UIApplicationDidChangeStatusBarFrameNotification - object:nil]; + NSDictionary *userInfo = [notification userInfo]; - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UIMenuControllerWillShowMenuNotification - object:nil]; + CGRect keyboardEndFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UIMenuControllerWillHideMenuNotification - object:nil]; + if (CGRectIsNull(keyboardEndFrame)) { + return; } -} -- (void)jsq_addActionToInteractivePopGestureRecognizer:(BOOL)addAction -{ - if (self.navigationController.interactivePopGestureRecognizer) { - [self.navigationController.interactivePopGestureRecognizer removeTarget:nil - action:@selector(jsq_handleInteractivePopGestureRecognizer:)]; + UIViewAnimationCurve animationCurve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + NSInteger animationCurveOption = (animationCurve << 16); - if (addAction) { - [self.navigationController.interactivePopGestureRecognizer addTarget:self - action:@selector(jsq_handleInteractivePopGestureRecognizer:)]; - } - } + 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 diff --git a/JSQMessagesViewController/Controllers/JSQMessagesViewController.xib b/JSQMessagesViewController/Controllers/JSQMessagesViewController.xib index 76e083a5f..4d29a1a3e 100644 --- a/JSQMessagesViewController/Controllers/JSQMessagesViewController.xib +++ b/JSQMessagesViewController/Controllers/JSQMessagesViewController.xib @@ -1,15 +1,14 @@ - + - + + - - @@ -24,11 +23,8 @@ - + - - - @@ -45,9 +41,4 @@ - - - - - diff --git a/JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.h b/JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.h index 759cd5388..8d4d0acf2 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.h +++ b/JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.h @@ -21,22 +21,41 @@ #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`. -* @param diameter An integer value specifying the diameter size of the avatar in points. This value must be greater than `0`. * -* @return An initialized `JSQMessagesAvatarImage` object if created successfully, `nil` otherwise. +* @return An initialized `JSQMessagesAvatarImage` object. */ -+ (JSQMessagesAvatarImage *)avatarImageWithPlaceholder:(UIImage *)placeholderImage diameter:(NSUInteger)diameter; +- (JSQMessagesAvatarImage *)avatarImageWithPlaceholder:(UIImage *)placeholderImage; /** * Creates and returns a `JSQMessagesAvatarImage` object with the specified image that is @@ -45,32 +64,29 @@ * 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`. - * @param diameter An integer value specifying the diameter size of the avatar in points. This value must be greater than `0`. * - * @return An initialized `JSQMessagesAvatarImage` object if created successfully, `nil` otherwise. + * @return An initialized `JSQMessagesAvatarImage` object. */ -+ (JSQMessagesAvatarImage *)avatarImageWithImage:(UIImage *)image diameter:(NSUInteger)diameter; +- (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`. - * @param diameter An integer value specifying the diameter size of the image in points. This value must be greater than `0`. * - * @return A new image object if successful, `nil` otherwise. + * @return A new image object. */ -+ (UIImage *)circularAvatarImage:(UIImage *)image withDiameter:(NSUInteger)diameter; +- (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`. - * @param diameter An integer value specifying the diameter size of the image in points. This value must be greater than `0`. * - * @return A new image object if successful, `nil` otherwise. + * @return A new image object. */ -+ (UIImage *)circularAvatarHighlightedImage:(UIImage *)image withDiameter:(NSUInteger)diameter; +- (UIImage *)circularAvatarHighlightedImage:(UIImage *)image; /** * Creates and returns a `JSQMessagesAvatarImage` object with a circular shape that displays the specified userInitials @@ -80,9 +96,8 @@ * @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`. - * @param diameter The diameter of the avatar image. This value must be greater than `0`. * - * @return An initialized `JSQMessagesAvatarImage` object if created successfully, `nil` otherwise. + * @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. @@ -90,10 +105,11 @@ * 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 +- (JSQMessagesAvatarImage *)avatarImageWithUserInitials:(NSString *)userInitials backgroundColor:(UIColor *)backgroundColor textColor:(UIColor *)textColor - font:(UIFont *)font - diameter:(NSUInteger)diameter; + font:(UIFont *)font; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.m b/JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.m index 6b187b571..c977aa55b 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.m +++ b/JSQMessagesViewController/Factories/JSQMessagesAvatarImageFactory.m @@ -20,75 +20,80 @@ #import "UIColor+JSQMessages.h" +// TODO: define kJSQMessagesCollectionViewAvatarSizeDefault elsewhere so we can remove this import +#import "JSQMessagesCollectionViewFlowLayout.h" @interface JSQMessagesAvatarImageFactory () -+ (UIImage *)jsq_circularImage:(UIImage *)image - withDiameter:(NSUInteger)diameter - highlightedColor:(UIColor *)highlightedColor; - -+ (UIImage *)jsq_imageWitInitials:(NSString *)initials - backgroundColor:(UIColor *)backgroundColor - textColor:(UIColor *)textColor - font:(UIFont *)font - diameter:(NSUInteger)diameter; +@property (assign, nonatomic, readonly) NSUInteger diameter; @end +@implementation JSQMessagesAvatarImageFactory +#pragma mark - Initialization -@implementation JSQMessagesAvatarImageFactory +- (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 diameter:(NSUInteger)diameter +- (JSQMessagesAvatarImage *)avatarImageWithPlaceholder:(UIImage *)placeholderImage { - UIImage *circlePlaceholderImage = [JSQMessagesAvatarImageFactory jsq_circularImage:placeholderImage - withDiameter:diameter - highlightedColor:nil]; + UIImage *circlePlaceholderImage = [self jsq_circularImage:placeholderImage + withHighlightedColor:nil]; return [JSQMessagesAvatarImage avatarImageWithPlaceholder:circlePlaceholderImage]; } -+ (JSQMessagesAvatarImage *)avatarImageWithImage:(UIImage *)image diameter:(NSUInteger)diameter +- (JSQMessagesAvatarImage *)avatarImageWithImage:(UIImage *)image { - UIImage *avatar = [JSQMessagesAvatarImageFactory circularAvatarImage:image withDiameter:diameter]; - UIImage *highlightedAvatar = [JSQMessagesAvatarImageFactory circularAvatarHighlightedImage:image withDiameter:diameter]; + UIImage *avatar = [self circularAvatarImage:image]; + UIImage *highlightedAvatar = [self circularAvatarHighlightedImage:image]; return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatar highlightedImage:highlightedAvatar placeholderImage:avatar]; } -+ (UIImage *)circularAvatarImage:(UIImage *)image withDiameter:(NSUInteger)diameter +- (UIImage *)circularAvatarImage:(UIImage *)image { - return [JSQMessagesAvatarImageFactory jsq_circularImage:image - withDiameter:diameter - highlightedColor:nil]; + return [self jsq_circularImage:image + withHighlightedColor:nil]; } -+ (UIImage *)circularAvatarHighlightedImage:(UIImage *)image withDiameter:(NSUInteger)diameter +- (UIImage *)circularAvatarHighlightedImage:(UIImage *)image { - return [JSQMessagesAvatarImageFactory jsq_circularImage:image - withDiameter:diameter - highlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]]; + return [self jsq_circularImage:image + withHighlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]]; } -+ (JSQMessagesAvatarImage *)avatarImageWithUserInitials:(NSString *)userInitials +- (JSQMessagesAvatarImage *)avatarImageWithUserInitials:(NSString *)userInitials backgroundColor:(UIColor *)backgroundColor textColor:(UIColor *)textColor font:(UIFont *)font - diameter:(NSUInteger)diameter { - UIImage *avatarImage = [JSQMessagesAvatarImageFactory jsq_imageWitInitials:userInitials - backgroundColor:backgroundColor - textColor:textColor - font:font - diameter:diameter]; + UIImage *avatarImage = [self jsq_imageWithInitials:userInitials + backgroundColor:backgroundColor + textColor:textColor + font:font]; - UIImage *avatarHighlightedImage = [JSQMessagesAvatarImageFactory jsq_circularImage:avatarImage - withDiameter:diameter - highlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]]; + UIImage *avatarHighlightedImage = [self jsq_circularImage:avatarImage + withHighlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]]; return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatarImage highlightedImage:avatarHighlightedImage @@ -97,19 +102,17 @@ + (JSQMessagesAvatarImage *)avatarImageWithUserInitials:(NSString *)userInitials #pragma mark - Private -+ (UIImage *)jsq_imageWitInitials:(NSString *)initials - backgroundColor:(UIColor *)backgroundColor - textColor:(UIColor *)textColor - font:(UIFont *)font - diameter:(NSUInteger)diameter +- (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); - NSParameterAssert(diameter > 0); - CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter); + CGRect frame = CGRectMake(0.0f, 0.0f, self.diameter, self.diameter); NSDictionary *attributes = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor }; @@ -140,15 +143,14 @@ + (UIImage *)jsq_imageWitInitials:(NSString *)initials } UIGraphicsEndImageContext(); - return [JSQMessagesAvatarImageFactory jsq_circularImage:image withDiameter:diameter highlightedColor:nil]; + return [self jsq_circularImage:image withHighlightedColor:nil]; } -+ (UIImage *)jsq_circularImage:(UIImage *)image withDiameter:(NSUInteger)diameter highlightedColor:(UIColor *)highlightedColor +- (UIImage *)jsq_circularImage:(UIImage *)image withHighlightedColor:(UIColor *)highlightedColor { NSParameterAssert(image != nil); - NSParameterAssert(diameter > 0); - CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter); + CGRect frame = CGRectMake(0.0f, 0.0f, self.diameter, self.diameter); UIImage *newImage = nil; UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale); diff --git a/JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.h b/JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.h index d883a129b..d87654ceb 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.h +++ b/JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.h @@ -21,17 +21,25 @@ #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 and cap insets. + * default bubble image assets, cap insets, and layout direction. * - * @return An initialized `JSQMessagesBubbleImageFactory` object if created successfully, `nil` otherwise. + * @return An initialized `JSQMessagesBubbleImageFactory` object. */ - (instancetype)init; @@ -47,9 +55,12 @@ * @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` object if created successfully, `nil` otherwise. + * @return An initialized `JSQMessagesBubbleImageFactory`. */ -- (instancetype)initWithBubbleImage:(UIImage *)bubbleImage capInsets:(UIEdgeInsets)capInsets; + +- (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. @@ -58,7 +69,7 @@ * * @param color The color of the bubble image in the image view. This value must not be `nil`. * - * @return An initialized `JSQMessagesBubbleImage` object if created successfully, `nil` otherwise. + * @return An initialized `JSQMessagesBubbleImage` object. */ - (JSQMessagesBubbleImage *)outgoingMessagesBubbleImageWithColor:(UIColor *)color; @@ -69,8 +80,10 @@ * * @param color The color of the bubble image in the image view. This value must not be `nil`. * - * @return An initialized `JSQMessagesBubbleImage` object if created successfully, `nil` otherwise. + * @return An initialized `JSQMessagesBubbleImage` object. */ - (JSQMessagesBubbleImage *)incomingMessagesBubbleImageWithColor:(UIColor *)color; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.m b/JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.m index 8910bc5ae..f86633c46 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.m +++ b/JSQMessagesViewController/Factories/JSQMessagesBubbleImageFactory.m @@ -25,66 +25,63 @@ @interface JSQMessagesBubbleImageFactory () @property (strong, nonatomic, readonly) UIImage *bubbleImage; -@property (assign, nonatomic, readonly) UIEdgeInsets capInsets; - -- (UIEdgeInsets)jsq_centerPointEdgeInsetsForImageSize:(CGSize)bubbleImageSize; - -- (JSQMessagesBubbleImage *)jsq_messagesBubbleImageWithColor:(UIColor *)color flippedForIncoming:(BOOL)flippedForIncoming; -- (UIImage *)jsq_horizontallyFlippedImageFromImage:(UIImage *)image; +@property (assign, nonatomic, readonly) UIEdgeInsets capInsets; -- (UIImage *)jsq_stretchableImageFromImage:(UIImage *)image withCapInsets:(UIEdgeInsets)capInsets; +@property (assign, nonatomic, readonly) BOOL isRightToLeftLanguage; @end - @implementation JSQMessagesBubbleImageFactory #pragma mark - Initialization -- (instancetype)initWithBubbleImage:(UIImage *)bubbleImage capInsets:(UIEdgeInsets)capInsets +- (instancetype)initWithBubbleImage:(UIImage *)bubbleImage + capInsets:(UIEdgeInsets)capInsets + layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection { - NSParameterAssert(bubbleImage != nil); - - self = [super init]; - if (self) { - _bubbleImage = bubbleImage; - + NSParameterAssert(bubbleImage != nil); + + self = [super init]; + if (self) { + _bubbleImage = bubbleImage; + _capInsets = capInsets; + _layoutDirection = layoutDirection; + if (UIEdgeInsetsEqualToEdgeInsets(capInsets, UIEdgeInsetsZero)) { _capInsets = [self jsq_centerPointEdgeInsetsForImageSize:bubbleImage.size]; } - else { - _capInsets = capInsets; - } - } - return self; + } + return self; } - (instancetype)init { - return [self initWithBubbleImage:[UIImage jsq_bubbleCompactImage] capInsets:UIEdgeInsetsZero]; -} - -- (void)dealloc -{ - _bubbleImage = nil; + 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]; + return [self jsq_messagesBubbleImageWithColor:color flippedForIncoming:NO ^ self.isRightToLeftLanguage]; } - (JSQMessagesBubbleImage *)incomingMessagesBubbleImageWithColor:(UIColor *)color { - return [self jsq_messagesBubbleImageWithColor:color flippedForIncoming:YES]; + 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 @@ -95,18 +92,18 @@ - (UIEdgeInsets)jsq_centerPointEdgeInsetsForImageSize:(CGSize)bubbleImageSize - (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]; } diff --git a/JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.h b/JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.h index 3bb94ab29..7de977b49 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.h +++ b/JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.h @@ -21,6 +21,8 @@ @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 @@ -42,7 +44,7 @@ * 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 if created successfully, `nil` otherwise. + * @return An initialized `JSQMessagesMediaViewBubbleImageMasker` object. * * @see JSQMessagesBubbleImageFactory. * @see JSQMessagesBubbleImage. @@ -56,12 +58,12 @@ * * @param bubbleImageFactory An initialized `JSQMessagesBubbleImageFactory` object to use for masking media views. This value must not be `nil`. * - * @return An initialized `JSQMessagesMediaViewBubbleImageMasker` object if created successfully, `nil` otherwise. + * @return An initialized `JSQMessagesMediaViewBubbleImageMasker` object. * * @see JSQMessagesBubbleImageFactory. * @see JSQMessagesBubbleImage. */ -- (instancetype)initWithBubbleImageFactory:(JSQMessagesBubbleImageFactory *)bubbleImageFactory; +- (instancetype)initWithBubbleImageFactory:(JSQMessagesBubbleImageFactory *)bubbleImageFactory NS_DESIGNATED_INITIALIZER; /** * Applies an outgoing bubble image mask to the specified mediaView. @@ -88,3 +90,5 @@ + (void)applyBubbleImageMaskToMediaView:(UIView *)mediaView isOutgoing:(BOOL)isOutgoing; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.m b/JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.m index 831804dd8..155b39134 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.m +++ b/JSQMessagesViewController/Factories/JSQMessagesMediaViewBubbleImageMasker.m @@ -21,13 +21,6 @@ #import "JSQMessagesBubbleImageFactory.h" -@interface JSQMessagesMediaViewBubbleImageMasker () - -- (void)jsq_maskView:(UIView *)view withImage:(UIImage *)image; - -@end - - @implementation JSQMessagesMediaViewBubbleImageMasker #pragma mark - Initialization diff --git a/JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.h b/JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.h index c7d48c236..03ddd2b00 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.h +++ b/JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.h @@ -19,6 +19,8 @@ #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. @@ -33,13 +35,13 @@ /** * 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 the bold system font at size `12.0f`. + * 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 the system font at size `12.0f`. + * The default value is a dictionary containing attributes that specify centered, light gray text and `UIFontTextStyleBody` font. */ @property (copy, nonatomic) NSDictionary *timeTextAttributes; @@ -93,3 +95,5 @@ - (NSString *)relativeDateForDate:(NSDate *)date; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.m b/JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.m index a7ee3ada0..35646644b 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.m +++ b/JSQMessagesViewController/Factories/JSQMessagesTimestampFormatter.m @@ -55,24 +55,17 @@ - (instancetype)init NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; paragraphStyle.alignment = NSTextAlignmentCenter; - _dateTextAttributes = @{ NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0f], + _dateTextAttributes = @{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : color, NSParagraphStyleAttributeName : paragraphStyle }; - _timeTextAttributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:12.0f], + _timeTextAttributes = @{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : color, NSParagraphStyleAttributeName : paragraphStyle }; } return self; } -- (void)dealloc -{ - _dateFormatter = nil; - _dateTextAttributes = nil; - _timeTextAttributes = nil; -} - #pragma mark - Formatter - (NSString *)timestampForDate:(NSDate *)date diff --git a/JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.h b/JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.h index 5659e4d77..3a19796a5 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.h +++ b/JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.h @@ -19,19 +19,39 @@ #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; +- (UIButton *)defaultAccessoryButtonItem; /** * Creates and returns a new button that is styled as the default send button. @@ -39,6 +59,8 @@ * * @return A newly created button. */ -+ (UIButton *)defaultSendButtonItem; +- (UIButton *)defaultSendButtonItem; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.m b/JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.m index 9d0b5788a..6aec36206 100644 --- a/JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.m +++ b/JSQMessagesViewController/Factories/JSQMessagesToolbarButtonFactory.m @@ -22,10 +22,32 @@ #import "UIImage+JSQMessages.h" #import "NSBundle+JSQMessages.h" +@interface JSQMessagesToolbarButtonFactory () + +@property (strong, nonatomic, readonly) UIFont *buttonFont; + +@end @implementation JSQMessagesToolbarButtonFactory -+ (UIButton *)defaultAccessoryButtonItem +- (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]]; @@ -38,11 +60,14 @@ + (UIButton *)defaultAccessoryButtonItem 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 +- (UIButton *)defaultSendButtonItem { NSString *sendTitle = [NSBundle jsq_localizedStringForKey:@"send"]; @@ -52,7 +77,7 @@ + (UIButton *)defaultSendButtonItem [sendButton setTitleColor:[[UIColor jsq_messageBubbleBlueColor] jsq_colorByDarkeningColorWithValue:0.1f] forState:UIControlStateHighlighted]; [sendButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; - sendButton.titleLabel.font = [UIFont boldSystemFontOfSize:17.0f]; + sendButton.titleLabel.font = self.buttonFont; sendButton.titleLabel.adjustsFontSizeToFitWidth = YES; sendButton.titleLabel.minimumScaleFactor = 0.85f; sendButton.contentMode = UIViewContentModeCenter; diff --git a/JSQMessagesViewController/Factories/JSQMessagesVideoThumbnailFactory.h b/JSQMessagesViewController/Factories/JSQMessagesVideoThumbnailFactory.h new file mode 100644 index 000000000..f20f821bb --- /dev/null +++ b/JSQMessagesViewController/Factories/JSQMessagesVideoThumbnailFactory.h @@ -0,0 +1,75 @@ +// +// 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 diff --git a/JSQMessagesViewController/Factories/JSQMessagesVideoThumbnailFactory.m b/JSQMessagesViewController/Factories/JSQMessagesVideoThumbnailFactory.m new file mode 100644 index 000000000..bf6c8a660 --- /dev/null +++ b/JSQMessagesViewController/Factories/JSQMessagesVideoThumbnailFactory.m @@ -0,0 +1,66 @@ +// +// 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 diff --git a/JSQMessagesViewController/JSQMessages.h b/JSQMessagesViewController/JSQMessages.h index fe1157466..e1333c339 100644 --- a/JSQMessagesViewController/JSQMessages.h +++ b/JSQMessagesViewController/JSQMessages.h @@ -29,9 +29,12 @@ #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" @@ -42,6 +45,7 @@ #import "JSQMessage.h" #import "JSQMediaItem.h" +#import "JSQAudioMediaItem.h" #import "JSQPhotoMediaItem.h" #import "JSQLocationMediaItem.h" #import "JSQVideoMediaItem.h" @@ -49,6 +53,8 @@ #import "JSQMessagesBubbleImage.h" #import "JSQMessagesAvatarImage.h" +#import "JSQAudioMediaViewAttributes.h" + // Protocols #import "JSQMessageData.h" #import "JSQMessageMediaData.h" @@ -56,6 +62,7 @@ #import "JSQMessageBubbleImageDataSource.h" #import "JSQMessagesCollectionViewDataSource.h" #import "JSQMessagesCollectionViewDelegateFlowLayout.h" +#import "JSQMessagesViewAccessoryButtonDelegate.h" // Factories #import "JSQMessagesAvatarImageFactory.h" @@ -65,7 +72,6 @@ #import "JSQMessagesToolbarButtonFactory.h" // Categories -#import "JSQSystemSoundPlayer+JSQMessages.h" #import "NSString+JSQMessages.h" #import "UIColor+JSQMessages.h" #import "UIImage+JSQMessages.h" diff --git a/JSQMessagesViewController/Layout/JSQAudioMediaViewAttributes.h b/JSQMessagesViewController/Layout/JSQAudioMediaViewAttributes.h new file mode 100644 index 000000000..03ce40212 --- /dev/null +++ b/JSQMessagesViewController/Layout/JSQAudioMediaViewAttributes.h @@ -0,0 +1,117 @@ +// +// 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 diff --git a/JSQMessagesViewController/Layout/JSQAudioMediaViewAttributes.m b/JSQMessagesViewController/Layout/JSQAudioMediaViewAttributes.m new file mode 100644 index 000000000..d53ff9e18 --- /dev/null +++ b/JSQMessagesViewController/Layout/JSQAudioMediaViewAttributes.m @@ -0,0 +1,114 @@ +// +// 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 diff --git a/JSQMessagesViewController/Layout/JSQMessagesBubbleSizeCalculating.h b/JSQMessagesViewController/Layout/JSQMessagesBubbleSizeCalculating.h new file mode 100644 index 000000000..1e31afa25 --- /dev/null +++ b/JSQMessagesViewController/Layout/JSQMessagesBubbleSizeCalculating.h @@ -0,0 +1,61 @@ +// +// 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 diff --git a/JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.h b/JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.h new file mode 100644 index 000000000..fd92ab1f1 --- /dev/null +++ b/JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.h @@ -0,0 +1,47 @@ +// +// 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 diff --git a/JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.m b/JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.m new file mode 100644 index 000000000..6b6038708 --- /dev/null +++ b/JSQMessagesViewController/Layout/JSQMessagesBubblesSizeCalculator.m @@ -0,0 +1,179 @@ +// +// 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 diff --git a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.h b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.h index d5efe5c71..4e8148070 100644 --- a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.h +++ b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.h @@ -23,6 +23,8 @@ #import +#import "JSQMessagesBubbleSizeCalculating.h" + @class JSQMessagesCollectionView; @@ -38,7 +40,7 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewCellLabelHeightDefault */ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault; - +NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesCollectionViewFlowLayout` is a concrete layout object that inherits @@ -63,6 +65,12 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault; @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`. * @@ -181,12 +189,15 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault; /** * Computes and returns the size of the `messageBubbleImageView` property of a `JSQMessagesCollectionViewCell` - * at the specified indexPath. 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. + * 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; @@ -200,3 +211,5 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesCollectionViewAvatarSizeDefault; - (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m index 53d191d75..099fca385 100644 --- a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m +++ b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m @@ -30,6 +30,7 @@ #import "JSQMessagesCollectionViewLayoutAttributes.h" #import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h" +#import "JSQMessagesBubblesSizeCalculator.h" #import "UIImage+JSQMessages.h" @@ -40,31 +41,11 @@ @interface JSQMessagesCollectionViewFlowLayout () -@property (strong, nonatomic) NSCache *messageBubbleCache; - @property (strong, nonatomic) UIDynamicAnimator *dynamicAnimator; @property (strong, nonatomic) NSMutableSet *visibleIndexPaths; @property (assign, nonatomic) CGFloat latestDelta; -@property (assign, nonatomic, readonly) NSUInteger bubbleImageAssetWidth; - -- (void)jsq_configureFlowLayout; - -- (void)jsq_didReceiveApplicationMemoryWarningNotification:(NSNotification *)notification; -- (void)jsq_didReceiveDeviceOrientationDidChangeNotification:(NSNotification *)notification; - -- (void)jsq_resetLayout; -- (void)jsq_resetDynamicAnimator; - -- (void)jsq_configureMessageCellLayoutAttributes:(JSQMessagesCollectionViewLayoutAttributes *)layoutAttributes; -- (CGSize)jsq_avatarSizeForIndexPath:(NSIndexPath *)indexPath; - -- (UIAttachmentBehavior *)jsq_springBehaviorWithLayoutAttributesItem:(UICollectionViewLayoutAttributes *)item; -- (void)jsq_addNewlyVisibleBehaviorsFromVisibleItems:(NSArray *)visibleItems; -- (void)jsq_removeNoLongerVisibleBehaviorsFromVisibleItemsIndexPaths:(NSSet *)visibleItemsIndexPaths; -- (void)jsq_adjustSpringBehavior:(UIAttachmentBehavior *)springBehavior forTouchLocation:(CGPoint)touchLocation; - @end @@ -73,6 +54,8 @@ @implementation JSQMessagesCollectionViewFlowLayout @dynamic collectionView; +@synthesize bubbleSizeCalculator = _bubbleSizeCalculator; + #pragma mark - Initialization - (void)jsq_configureFlowLayout @@ -81,12 +64,6 @@ - (void)jsq_configureFlowLayout self.sectionInset = UIEdgeInsetsMake(10.0f, 4.0f, 10.0f, 4.0f); self.minimumLineSpacing = 4.0f; - _bubbleImageAssetWidth = [UIImage jsq_bubbleCompactImage].size.width; - - _messageBubbleCache = [NSCache new]; - _messageBubbleCache.name = @"JSQMessagesCollectionViewFlowLayout.messageBubbleCache"; - _messageBubbleCache.countLimit = 200; - _messageBubbleFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { @@ -145,21 +122,16 @@ + (Class)invalidationContextClass - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - - _messageBubbleFont = nil; - - [_messageBubbleCache removeAllObjects]; - _messageBubbleCache = nil; - - [_dynamicAnimator removeAllBehaviors]; - _dynamicAnimator = nil; - - [_visibleIndexPaths removeAllObjects]; - _visibleIndexPaths = nil; } #pragma mark - Setters +- (void)setBubbleSizeCalculator:(id)bubbleSizeCalculator +{ + NSParameterAssert(bubbleSizeCalculator != nil); + _bubbleSizeCalculator = bubbleSizeCalculator; +} + - (void)setSpringinessEnabled:(BOOL)springinessEnabled { if (_springinessEnabled == springinessEnabled) { @@ -223,11 +195,6 @@ - (void)setOutgoingAvatarViewSize:(CGSize)outgoingAvatarViewSize [self invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]]; } -- (void)setCacheLimit:(NSUInteger)cacheLimit -{ - self.messageBubbleCache.countLimit = cacheLimit; -} - #pragma mark - Getters - (CGFloat)itemWidth @@ -251,9 +218,13 @@ - (NSMutableSet *)visibleIndexPaths return _visibleIndexPaths; } -- (NSUInteger)cacheLimit +- (id)bubbleSizeCalculator { - return self.messageBubbleCache.countLimit; + if (_bubbleSizeCalculator == nil) { + _bubbleSizeCalculator = [JSQMessagesBubblesSizeCalculator new]; + } + + return _bubbleSizeCalculator; } #pragma mark - Notifications @@ -299,7 +270,7 @@ - (void)prepareLayout CGFloat padding = -100.0f; CGRect visibleRect = CGRectInset(self.collectionView.bounds, padding, padding); - NSArray *visibleItems = [super layoutAttributesForElementsInRect:visibleRect]; + NSArray *visibleItems = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:visibleRect] copyItems:YES]; NSSet *visibleItemsIndexPaths = [NSSet setWithArray:[visibleItems valueForKey:NSStringFromSelector(@selector(indexPath))]]; [self jsq_removeNoLongerVisibleBehaviorsFromVisibleItemsIndexPaths:visibleItemsIndexPaths]; @@ -310,7 +281,7 @@ - (void)prepareLayout - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { - NSArray *attributesInRect = [super layoutAttributesForElementsInRect:rect]; + NSArray *attributesInRect = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES]; if (self.springinessEnabled) { NSMutableArray *attributesInRectCopy = [attributesInRect mutableCopy]; @@ -331,7 +302,7 @@ - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect } } - attributesInRect = attributesInRectCopy; + attributesInRect = [attributesInRectCopy copy]; } [attributesInRect enumerateObjectsUsingBlock:^(JSQMessagesCollectionViewLayoutAttributes *attributesItem, NSUInteger idx, BOOL *stop) { @@ -348,7 +319,7 @@ - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { - JSQMessagesCollectionViewLayoutAttributes *customAttributes = (JSQMessagesCollectionViewLayoutAttributes *)[super layoutAttributesForItemAtIndexPath:indexPath]; + JSQMessagesCollectionViewLayoutAttributes *customAttributes = (JSQMessagesCollectionViewLayoutAttributes *)[[super layoutAttributesForItemAtIndexPath:indexPath] copy]; if (customAttributes.representedElementCategory == UICollectionElementCategoryCell) { [self jsq_configureMessageCellLayoutAttributes:customAttributes]; @@ -417,7 +388,7 @@ - (void)prepareForCollectionViewUpdates:(NSArray *)updateItems - (void)jsq_resetLayout { - [self.messageBubbleCache removeAllObjects]; + [self.bubbleSizeCalculator prepareForResettingLayout:self]; [self jsq_resetDynamicAnimator]; } @@ -433,52 +404,12 @@ - (void)jsq_resetDynamicAnimator - (CGSize)messageBubbleSizeForItemAtIndexPath:(NSIndexPath *)indexPath { - id messageItem = [self.collectionView.dataSource collectionView:self.collectionView messageDataForItemAtIndexPath:indexPath]; - - NSValue *cachedSize = [self.messageBubbleCache objectForKey:@([messageItem messageHash])]; - if (cachedSize != nil) { - return [cachedSize CGSizeValue]; - } - - CGSize finalSize = CGSizeZero; - - if ([messageItem isMediaMessage]) { - finalSize = [[messageItem media] mediaViewDisplaySize]; - } - else { - CGSize avatarSize = [self jsq_avatarSizeForIndexPath:indexPath]; - - // from the cell xibs, there is a 2 point space between avatar and bubble - CGFloat spacingBetweenAvatarAndBubble = 2.0f; - CGFloat horizontalContainerInsets = self.messageBubbleTextViewTextContainerInsets.left + self.messageBubbleTextViewTextContainerInsets.right; - CGFloat horizontalFrameInsets = self.messageBubbleTextViewFrameInsets.left + self.messageBubbleTextViewFrameInsets.right; - - CGFloat horizontalInsetsTotal = horizontalContainerInsets + horizontalFrameInsets + spacingBetweenAvatarAndBubble; - CGFloat maximumTextWidth = self.itemWidth - avatarSize.width - self.messageBubbleLeftRightMargin - horizontalInsetsTotal; - - CGRect stringRect = [[messageItem text] boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX) - options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) - attributes:@{ NSFontAttributeName : self.messageBubbleFont } - context:nil]; - - CGSize stringSize = CGRectIntegral(stringRect).size; - - CGFloat verticalContainerInsets = self.messageBubbleTextViewTextContainerInsets.top + self.messageBubbleTextViewTextContainerInsets.bottom; - CGFloat verticalFrameInsets = self.messageBubbleTextViewFrameInsets.top + self.messageBubbleTextViewFrameInsets.bottom; - - // add extra 2 points of space, because `boundingRectWithSize:` is slightly off - // not sure why. magix. (shrug) if you know, submit a PR - CGFloat verticalInsets = verticalContainerInsets + verticalFrameInsets + 2.0f; - - // same as above, an extra 2 points of magix - CGFloat finalWidth = MAX(stringSize.width + horizontalInsetsTotal, self.bubbleImageAssetWidth) + 2.0f; - - finalSize = CGSizeMake(finalWidth, stringSize.height + verticalInsets); - } - - [self.messageBubbleCache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageItem messageHash])]; - - return finalSize; + id messageItem = [self.collectionView.dataSource collectionView:self.collectionView + messageDataForItemAtIndexPath:indexPath]; + + return [self.bubbleSizeCalculator messageBubbleSizeForMessageData:messageItem + atIndexPath:indexPath + withLayout:self]; } - (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath @@ -525,18 +456,6 @@ - (void)jsq_configureMessageCellLayoutAttributes:(JSQMessagesCollectionViewLayou heightForCellBottomLabelAtIndexPath:indexPath]; } -- (CGSize)jsq_avatarSizeForIndexPath:(NSIndexPath *)indexPath -{ - id messageData = [self.collectionView.dataSource collectionView:self.collectionView messageDataForItemAtIndexPath:indexPath]; - NSString *messageSender = [messageData senderId]; - - if ([messageSender isEqualToString:[self.collectionView.dataSource senderId]]) { - return self.outgoingAvatarViewSize; - } - - return self.incomingAvatarViewSize; -} - #pragma mark - Spring behavior utilities - (UIAttachmentBehavior *)jsq_springBehaviorWithLayoutAttributesItem:(UICollectionViewLayoutAttributes *)item @@ -577,14 +496,14 @@ - (void)jsq_removeNoLongerVisibleBehaviorsFromVisibleItemsIndexPaths:(NSSet *)vi NSArray *behaviors = self.dynamicAnimator.behaviors; NSIndexSet *indexSet = [behaviors indexesOfObjectsPassingTest:^BOOL(UIAttachmentBehavior *springBehaviour, NSUInteger index, BOOL *stop) { - UICollectionViewLayoutAttributes *layoutAttributes = [springBehaviour.items firstObject]; + 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 = [springBehaviour.items firstObject]; + UICollectionViewLayoutAttributes *layoutAttributes = (UICollectionViewLayoutAttributes *)[springBehaviour.items firstObject]; [self.dynamicAnimator removeBehavior:springBehaviour]; [self.visibleIndexPaths removeObject:layoutAttributes.indexPath]; }]; @@ -592,7 +511,7 @@ - (void)jsq_removeNoLongerVisibleBehaviorsFromVisibleItemsIndexPaths:(NSSet *)vi - (void)jsq_adjustSpringBehavior:(UIAttachmentBehavior *)springBehavior forTouchLocation:(CGPoint)touchLocation { - UICollectionViewLayoutAttributes *item = [springBehavior.items firstObject]; + UICollectionViewLayoutAttributes *item = (UICollectionViewLayoutAttributes *)[springBehavior.items firstObject]; CGPoint center = item.center; // if touch is not (0,0) -- adjust item center "in flight" diff --git a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayoutInvalidationContext.h b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayoutInvalidationContext.h index b15c46fd4..82b9a71aa 100644 --- a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayoutInvalidationContext.h +++ b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayoutInvalidationContext.h @@ -18,6 +18,8 @@ #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. @@ -40,8 +42,10 @@ * `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 if successful, otherwise `nil`. + * @return An initialized invalidation context object. */ + (instancetype)context; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.h b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.h index 6b414182d..cfc106399 100644 --- a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.h +++ b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.h @@ -18,6 +18,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + /** * A `JSQMessagesCollectionViewLayoutAttributes` is an object that manages the layout-related attributes * for a given `JSQMessagesCollectionViewCell` in a `JSQMessagesCollectionView`. @@ -104,3 +106,5 @@ @property (assign, nonatomic) CGFloat cellBottomLabelHeight; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.m b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.m index 720f4289b..9dcbbbafb 100644 --- a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.m +++ b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewLayoutAttributes.m @@ -18,23 +18,17 @@ #import "JSQMessagesCollectionViewLayoutAttributes.h" - -@interface JSQMessagesCollectionViewLayoutAttributes () - -- (CGSize)jsq_correctedAvatarSizeFromSize:(CGSize)size; - -- (CGFloat)jsq_correctedLabelHeightForHeight:(CGFloat)height; - -@end - - @implementation JSQMessagesCollectionViewLayoutAttributes -#pragma mark - Lifecycle +#pragma mark - Init -- (void)dealloc -{ - _messageBubbleFont = nil; +- (instancetype)init { + self = [super init]; + if (self) { + _messageBubbleFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; + _messageBubbleContainerViewWidth = 320.0f; + } + return self; } #pragma mark - Setters diff --git a/JSQMessagesViewController/Model/JSQAudioMediaItem.h b/JSQMessagesViewController/Model/JSQAudioMediaItem.h new file mode 100644 index 000000000..e40d7295f --- /dev/null +++ b/JSQMessagesViewController/Model/JSQAudioMediaItem.h @@ -0,0 +1,119 @@ +// +// 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 diff --git a/JSQMessagesViewController/Model/JSQAudioMediaItem.m b/JSQMessagesViewController/Model/JSQAudioMediaItem.m new file mode 100644 index 000000000..91dad9ec5 --- /dev/null +++ b/JSQMessagesViewController/Model/JSQAudioMediaItem.m @@ -0,0 +1,374 @@ +// +// 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 diff --git a/JSQMessagesViewController/Model/JSQLocationMediaItem.h b/JSQMessagesViewController/Model/JSQLocationMediaItem.h index 83b563ef4..d3ff3be6b 100644 --- a/JSQMessagesViewController/Model/JSQLocationMediaItem.h +++ b/JSQMessagesViewController/Model/JSQLocationMediaItem.h @@ -27,6 +27,8 @@ 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 @@ -38,7 +40,7 @@ typedef void (^JSQLocationMediaItemCompletionBlock)(void); /** * The location for the media item. The default value is `nil`. */ -@property (copy, nonatomic) CLLocation *location; +@property (copy, nonatomic, nullable) CLLocation *location; /** * The coordinate of the location property. @@ -50,14 +52,14 @@ typedef void (^JSQLocationMediaItemCompletionBlock)(void); * * @param location The location for the media item. This value may be `nil`. * - * @return An initialized `JSQLocationMediaItem` if successful, `nil` otherwise. + * @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:(CLLocation *)location; +- (instancetype)initWithLocation:(nullable CLLocation *)location; /** * Sets the specified location for the location media item and immediately begins creating @@ -69,7 +71,7 @@ typedef void (^JSQLocationMediaItemCompletionBlock)(void); * @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:(CLLocation *)location withCompletionHandler:(JSQLocationMediaItemCompletionBlock)completion; +- (void)setLocation:(nullable CLLocation *)location withCompletionHandler:(nullable JSQLocationMediaItemCompletionBlock)completion; /** * Sets the specified location for the location media item and immediately begins creating @@ -81,6 +83,8 @@ typedef void (^JSQLocationMediaItemCompletionBlock)(void); * @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:(CLLocation *)location - region:(MKCoordinateRegion)region withCompletionHandler:(JSQLocationMediaItemCompletionBlock)completion; +- (void)setLocation:(nullable CLLocation *)location + region:(MKCoordinateRegion)region withCompletionHandler:(nullable JSQLocationMediaItemCompletionBlock)completion; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQLocationMediaItem.m b/JSQMessagesViewController/Model/JSQLocationMediaItem.m index 9197e1743..e0aa13f6b 100644 --- a/JSQMessagesViewController/Model/JSQLocationMediaItem.m +++ b/JSQMessagesViewController/Model/JSQLocationMediaItem.m @@ -21,6 +21,8 @@ #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" +#import + @interface JSQLocationMediaItem () @@ -28,10 +30,6 @@ @interface JSQLocationMediaItem () @property (strong, nonatomic) UIImageView *cachedMapImageView; -- (void)createMapViewSnapshotForLocation:(CLLocation *)location - coordinateRegion:(MKCoordinateRegion)region - withCompletionHandler:(JSQLocationMediaItemCompletionBlock)completion; - @end @@ -48,13 +46,6 @@ - (instancetype)initWithLocation:(CLLocation *)location return self; } -- (void)dealloc -{ - _location = nil; - _cachedMapSnapshotImage = nil; - _cachedMapImageView = nil; -} - - (void)clearCachedMediaViews { [super clearCachedMediaViews]; @@ -112,7 +103,7 @@ - (void)createMapViewSnapshotForLocation:(CLLocation *)location [snapShotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - if (error) { + if (snapshot == nil) { NSLog(@"%s Error creating map snapshot: %@", __PRETTY_FUNCTION__, error); return; } @@ -121,6 +112,9 @@ - (void)createMapViewSnapshotForLocation:(CLLocation *)location 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]; @@ -166,6 +160,19 @@ - (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 diff --git a/JSQMessagesViewController/Model/JSQMediaItem.h b/JSQMessagesViewController/Model/JSQMediaItem.h index f38756c60..05584d367 100644 --- a/JSQMessagesViewController/Model/JSQMediaItem.h +++ b/JSQMessagesViewController/Model/JSQMediaItem.h @@ -18,6 +18,8 @@ #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, @@ -46,7 +48,7 @@ * @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 if successful, `nil` otherwise. + * @return An initialized `JSQMediaItem` object. */ - (instancetype)initWithMaskAsOutgoing:(BOOL)maskAsOutgoing; @@ -56,3 +58,5 @@ - (void)clearCachedMediaViews; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQMediaItem.m b/JSQMessagesViewController/Model/JSQMediaItem.m index 3cc504763..32e3adc4c 100644 --- a/JSQMessagesViewController/Model/JSQMediaItem.m +++ b/JSQMessagesViewController/Model/JSQMediaItem.m @@ -55,7 +55,6 @@ - (instancetype)initWithMaskAsOutgoing:(BOOL)maskAsOutgoing - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - _cachedPlaceholderView = nil; } - (void)setAppliesMediaViewMaskAsOutgoing:(BOOL)appliesMediaViewMaskAsOutgoing diff --git a/JSQMessagesViewController/Model/JSQMessage.h b/JSQMessagesViewController/Model/JSQMessage.h index 68624a4f2..1b18d4aa8 100644 --- a/JSQMessagesViewController/Model/JSQMessage.h +++ b/JSQMessagesViewController/Model/JSQMessage.h @@ -20,6 +20,8 @@ #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. @@ -55,13 +57,13 @@ * 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) NSString *text; +@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) id media; +@property (copy, nonatomic, readonly, null_unspecified) id media; #pragma mark - Initialization @@ -76,7 +78,7 @@ * * @discussion Initializing a `JSQMessage` with this method will set `isMediaMessage` to `NO`. * - * @return An initialized `JSQMessage` object if successful, `nil` otherwise. + * @return An initialized `JSQMessage` object. */ + (instancetype)messageWithSenderId:(NSString *)senderId displayName:(NSString *)displayName @@ -92,7 +94,7 @@ * * @discussion Initializing a `JSQMessage` with this method will set `isMediaMessage` to `NO`. * - * @return An initialized `JSQMessage` object if successful, `nil` otherwise. + * @return An initialized `JSQMessage` object. */ - (instancetype)initWithSenderId:(NSString *)senderId senderDisplayName:(NSString *)senderDisplayName @@ -108,7 +110,7 @@ * * @discussion Initializing a `JSQMessage` with this method will set `isMediaMessage` to `YES`. * - * @return An initialized `JSQMessage` object if successful, `nil` otherwise. + * @return An initialized `JSQMessage` object. */ + (instancetype)messageWithSenderId:(NSString *)senderId displayName:(NSString *)displayName @@ -124,11 +126,18 @@ * * @discussion Initializing a `JSQMessage` with this method will set `isMediaMessage` to `YES`. * - * @return An initialized `JSQMessage` object if successful, `nil` otherwise. + * @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 diff --git a/JSQMessagesViewController/Model/JSQMessage.m b/JSQMessagesViewController/Model/JSQMessage.m index 8bc9650ab..b326fddc1 100644 --- a/JSQMessagesViewController/Model/JSQMessage.m +++ b/JSQMessagesViewController/Model/JSQMessage.m @@ -19,17 +19,6 @@ #import "JSQMessage.h" -@interface JSQMessage () - -- (instancetype)initWithSenderId:(NSString *)senderId - senderDisplayName:(NSString *)senderDisplayName - date:(NSDate *)date - isMedia:(BOOL)isMedia; - -@end - - - @implementation JSQMessage #pragma mark - Initialization @@ -38,10 +27,10 @@ + (instancetype)messageWithSenderId:(NSString *)senderId displayName:(NSString *)displayName text:(NSString *)text { - return [[JSQMessage alloc] initWithSenderId:senderId - senderDisplayName:displayName - date:[NSDate date] - text:text]; + return [[self alloc] initWithSenderId:senderId + senderDisplayName:displayName + date:[NSDate date] + text:text]; } - (instancetype)initWithSenderId:(NSString *)senderId @@ -50,7 +39,7 @@ - (instancetype)initWithSenderId:(NSString *)senderId text:(NSString *)text { NSParameterAssert(text != nil); - + self = [self initWithSenderId:senderId senderDisplayName:senderDisplayName date:date isMedia:NO]; if (self) { _text = [text copy]; @@ -62,10 +51,10 @@ + (instancetype)messageWithSenderId:(NSString *)senderId displayName:(NSString *)displayName media:(id)media { - return [[JSQMessage alloc] initWithSenderId:senderId - senderDisplayName:displayName - date:[NSDate date] - media:media]; + return [[self alloc] initWithSenderId:senderId + senderDisplayName:displayName + date:[NSDate date] + media:media]; } - (instancetype)initWithSenderId:(NSString *)senderId @@ -74,7 +63,7 @@ - (instancetype)initWithSenderId:(NSString *)senderId media:(id)media { NSParameterAssert(media != nil); - + self = [self initWithSenderId:senderId senderDisplayName:senderDisplayName date:date isMedia:YES]; if (self) { _media = media; @@ -90,7 +79,7 @@ - (instancetype)initWithSenderId:(NSString *)senderId NSParameterAssert(senderId != nil); NSParameterAssert(senderDisplayName != nil); NSParameterAssert(date != nil); - + self = [super init]; if (self) { _senderId = [senderId copy]; @@ -101,21 +90,6 @@ - (instancetype)initWithSenderId:(NSString *)senderId return self; } -- (id)init -{ - NSAssert(NO, @"%s is not a valid initializer for %@.", __PRETTY_FUNCTION__, [self class]); - return nil; -} - -- (void)dealloc -{ - _senderId = nil; - _senderDisplayName = nil; - _date = nil; - _text = nil; - _media = nil; -} - - (NSUInteger)messageHash { return self.hash; @@ -128,23 +102,23 @@ - (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; + && [self.senderDisplayName isEqualToString:aMessage.senderDisplayName] + && ([self.date compare:aMessage.date] == NSOrderedSame) + && hasEqualContent; } - (NSUInteger)hash @@ -187,7 +161,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder [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))]; } @@ -203,7 +177,7 @@ - (instancetype)copyWithZone:(NSZone *)zone date:self.date media:self.media]; } - + return [[[self class] allocWithZone:zone] initWithSenderId:self.senderId senderDisplayName:self.senderDisplayName date:self.date diff --git a/JSQMessagesViewController/Model/JSQMessageAvatarImageDataSource.h b/JSQMessagesViewController/Model/JSQMessageAvatarImageDataSource.h index fc31072b1..d17f98991 100644 --- a/JSQMessagesViewController/Model/JSQMessageAvatarImageDataSource.h +++ b/JSQMessagesViewController/Model/JSQMessageAvatarImageDataSource.h @@ -19,6 +19,8 @@ #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. @@ -39,14 +41,14 @@ * * @discussion You may return `nil` from this method while the image is being downloaded. */ -- (UIImage *)avatarImage; +- (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. */ -- (UIImage *)avatarHighlightedImage; +- (nullable UIImage *)avatarHighlightedImage; /** * @return A placeholder avatar image to be displayed if avatarImage is not yet available, or `nil`. @@ -61,3 +63,5 @@ - (UIImage *)avatarPlaceholderImage; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQMessageBubbleImageDataSource.h b/JSQMessagesViewController/Model/JSQMessageBubbleImageDataSource.h index 26d4afd80..225fa94b1 100644 --- a/JSQMessagesViewController/Model/JSQMessageBubbleImageDataSource.h +++ b/JSQMessagesViewController/Model/JSQMessageBubbleImageDataSource.h @@ -19,6 +19,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + /** * The `JSQMessageBubbleImageDataSource` protocol defines the common interface through which * a `JSQMessagesViewController` and `JSQMessagesCollectionView` interact with @@ -50,3 +52,5 @@ - (UIImage *)messageBubbleHighlightedImage; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQMessageData.h b/JSQMessagesViewController/Model/JSQMessageData.h index d83d11df4..b57e8eef3 100644 --- a/JSQMessagesViewController/Model/JSQMessageData.h +++ b/JSQMessagesViewController/Model/JSQMessageData.h @@ -20,6 +20,8 @@ #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. @@ -27,11 +29,9 @@ * 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`. * - * Two concrete classes that conform to this protocol are provided in the library. See `JSQTextMessage` and `JSQMediaMessage`. + * The class that conforms to this protocol is provided in the library. See `JSQMessage`. * * @see JSQMessage. - * @see JSQTextMessage. - * @see JSQMediaMessage. */ @protocol JSQMessageData @@ -100,3 +100,5 @@ - (id)media; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQMessageMediaData.h b/JSQMessagesViewController/Model/JSQMessageMediaData.h index 4c8ab63af..7a7d4f1e6 100644 --- a/JSQMessagesViewController/Model/JSQMessageMediaData.h +++ b/JSQMessagesViewController/Model/JSQMessageMediaData.h @@ -19,6 +19,8 @@ #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. @@ -43,7 +45,7 @@ * * @discussion You may return `nil` from this method while the media data is being downloaded. */ -- (UIView *)mediaView; +- (nullable UIView *)mediaView; /** * @return The frame size for the mediaView when displayed in a `JSQMessagesCollectionViewCell`. @@ -78,4 +80,26 @@ */ - (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 diff --git a/JSQMessagesViewController/Model/JSQMessagesAvatarImage.h b/JSQMessagesViewController/Model/JSQMessagesAvatarImage.h index f79932013..9eb43b267 100644 --- a/JSQMessagesViewController/Model/JSQMessagesAvatarImage.h +++ b/JSQMessagesViewController/Model/JSQMessagesAvatarImage.h @@ -21,6 +21,8 @@ #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. @@ -33,12 +35,12 @@ /** * The avatar image for a regular display state. */ -@property (nonatomic, strong) UIImage *avatarImage; +@property (nonatomic, strong, nullable) UIImage *avatarImage; /** * The avatar image for a highlighted display state. */ -@property (nonatomic, strong) UIImage *avatarHighlightedImage; +@property (nonatomic, strong, nullable) UIImage *avatarHighlightedImage; /** * Returns the placeholder image for an avatar to display if avatarImage is `nil`. @@ -52,7 +54,7 @@ * properties: avatarImage, avatarHighlightedImage, avatarPlaceholderImage; * This value must not be `nil`. * - * @return An initialized `JSQMessagesAvatarImage` object if successful, `nil` otherwise. + * @return An initialized `JSQMessagesAvatarImage` object. */ + (instancetype)avatarWithImage:(UIImage *)image; @@ -61,7 +63,7 @@ * * @param placeholderImage The placeholder image for this avatar image. This value must not be `nil`. * - * @return An initialized `JSQMessagesAvatarImage` object if successful, `nil` otherwise. + * @return An initialized `JSQMessagesAvatarImage` object. */ + (instancetype)avatarImageWithPlaceholder:(UIImage *)placeholderImage; @@ -72,10 +74,17 @@ * @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 if successful, `nil` otherwise. + * @return An initialized `JSQMessagesAvatarImage` object. */ -- (instancetype)initWithAvatarImage:(UIImage *)avatarImage - highlightedImage:(UIImage *)highlightedImage - placeholderImage:(UIImage *)placeholderImage; +- (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 diff --git a/JSQMessagesViewController/Model/JSQMessagesAvatarImage.m b/JSQMessagesViewController/Model/JSQMessagesAvatarImage.m index f75973751..20828611a 100644 --- a/JSQMessagesViewController/Model/JSQMessagesAvatarImage.m +++ b/JSQMessagesViewController/Model/JSQMessagesAvatarImage.m @@ -53,13 +53,6 @@ - (instancetype)initWithAvatarImage:(UIImage *)avatarImage return self; } -- (id)init -{ - NSAssert(NO, @"%s is not a valid initializer for %@. Use %@ instead.", - __PRETTY_FUNCTION__, [self class], NSStringFromSelector(@selector(initWithAvatarImage:highlightedImage:placeholderImage:))); - return nil; -} - #pragma mark - NSObject - (NSString *)description diff --git a/JSQMessagesViewController/Model/JSQMessagesBubbleImage.h b/JSQMessagesViewController/Model/JSQMessagesBubbleImage.h index 9bfc2bf8f..0316c4499 100644 --- a/JSQMessagesViewController/Model/JSQMessagesBubbleImage.h +++ b/JSQMessagesViewController/Model/JSQMessagesBubbleImage.h @@ -21,6 +21,8 @@ #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. @@ -46,10 +48,17 @@ * @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 if successful, `nil` otherwise. + * @return An initialized `JSQMessagesBubbleImage` object. * * @see JSQMessagesBubbleImageFactory. */ -- (instancetype)initWithMessageBubbleImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage; +- (instancetype)initWithMessageBubbleImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage NS_DESIGNATED_INITIALIZER; + +/** + * Not a valid initializer. + */ +- (id)init NS_UNAVAILABLE; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQMessagesBubbleImage.m b/JSQMessagesViewController/Model/JSQMessagesBubbleImage.m index 4dbc5c2a4..047b9c085 100644 --- a/JSQMessagesViewController/Model/JSQMessagesBubbleImage.m +++ b/JSQMessagesViewController/Model/JSQMessagesBubbleImage.m @@ -35,13 +35,6 @@ - (instancetype)initWithMessageBubbleImage:(UIImage *)image highlightedImage:(UI return self; } -- (id)init -{ - NSAssert(NO, @"%s is not a valid initializer for %@. Use %@ instead.", - __PRETTY_FUNCTION__, [self class], NSStringFromSelector(@selector(initWithMessageBubbleImage:highlightedImage:))); - return nil; -} - #pragma mark - NSObject - (NSString *)description diff --git a/JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h b/JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h index 2040b71f0..ba0e6e0b5 100644 --- a/JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h +++ b/JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h @@ -24,6 +24,7 @@ @protocol JSQMessageBubbleImageDataSource; @protocol JSQMessageAvatarImageDataSource; +NS_ASSUME_NONNULL_BEGIN /** * An object that adopts the `JSQMessagesCollectionViewDataSource` protocol is responsible for providing the data and views @@ -55,17 +56,26 @@ /** * Asks the data source for the message data that corresponds to the specified item at indexPath in the collectionView. * - * @param collectionView The object representing the collection view requesting this information. + * @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 object representing the collection view requesting this information. + * @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 @@ -80,12 +90,12 @@ * @see JSQMessagesBubbleImageFactory. * @see JSQMessagesCollectionViewFlowLayout. */ -- (id)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath; +- (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 object representing the collection view requesting this information. + * @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 @@ -97,7 +107,7 @@ * @see JSQMessagesAvatarImageFactory. * @see JSQMessagesCollectionViewFlowLayout. */ -- (id)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath; +- (nullable id)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath; @optional @@ -105,7 +115,7 @@ * 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 object representing the collection view requesting this information. + * @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. @@ -113,13 +123,13 @@ * * @see JSQMessagesCollectionViewCell. */ -- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath; +- (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 object representing the collection view requesting this information. + * @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. @@ -127,13 +137,13 @@ * * @see JSQMessagesCollectionViewCell. */ -- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath; +- (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 object representing the collection view requesting this information. + * @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. @@ -141,6 +151,8 @@ * * @see JSQMessagesCollectionViewCell. */ -- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath; +- (nullable NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQMessagesCollectionViewDelegateFlowLayout.h b/JSQMessagesViewController/Model/JSQMessagesCollectionViewDelegateFlowLayout.h index 6e9217ae7..a7417969a 100644 --- a/JSQMessagesViewController/Model/JSQMessagesCollectionViewDelegateFlowLayout.h +++ b/JSQMessagesViewController/Model/JSQMessagesCollectionViewDelegateFlowLayout.h @@ -24,6 +24,7 @@ @class JSQMessagesCollectionViewCell; @class JSQMessagesLoadEarlierHeaderView; +NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesCollectionViewDelegateFlowLayout` protocol defines methods that allow you to @@ -120,3 +121,5 @@ header:(JSQMessagesLoadEarlierHeaderView *)headerView didTapLoadEarlierMessagesButton:(UIButton *)sender; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQMessagesViewAccessoryButtonDelegate.h b/JSQMessagesViewController/Model/JSQMessagesViewAccessoryButtonDelegate.h new file mode 100644 index 000000000..ca8948c5e --- /dev/null +++ b/JSQMessagesViewController/Model/JSQMessagesViewAccessoryButtonDelegate.h @@ -0,0 +1,42 @@ +// +// Created by Jesse Squires +// http://www.jessesquires.com +// +// Documentation +// http://cocoadocs.org/docsets/JSQMessagesViewController +// +// +// GitHub +// https://github.com/jessesquires/JSQMessagesViewController +// +// +// License +// Copyright (c) 2014 Jesse Squires +// Released under an MIT license: http://opensource.org/licenses/MIT +// + +#import + +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 diff --git a/JSQMessagesViewController/Model/JSQPhotoMediaItem.h b/JSQMessagesViewController/Model/JSQPhotoMediaItem.h index e0f112d33..4c5cbbb03 100644 --- a/JSQMessagesViewController/Model/JSQPhotoMediaItem.h +++ b/JSQMessagesViewController/Model/JSQPhotoMediaItem.h @@ -18,6 +18,8 @@ #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 @@ -29,19 +31,21 @@ /** * The image for the photo media item. The default value is `nil`. */ -@property (copy, nonatomic) UIImage *image; +@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` if successful, `nil` otherwise. + * @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:(UIImage *)image; +- (instancetype)initWithImage:(nullable UIImage *)image; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Model/JSQPhotoMediaItem.m b/JSQMessagesViewController/Model/JSQPhotoMediaItem.m index e14d25cc3..e7cbf4bb0 100644 --- a/JSQMessagesViewController/Model/JSQPhotoMediaItem.m +++ b/JSQMessagesViewController/Model/JSQPhotoMediaItem.m @@ -21,6 +21,7 @@ #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" +#import @interface JSQPhotoMediaItem () @@ -43,12 +44,6 @@ - (instancetype)initWithImage:(UIImage *)image return self; } -- (void)dealloc -{ - _image = nil; - _cachedImageView = nil; -} - - (void)clearCachedMediaViews { [super clearCachedMediaViews]; @@ -95,6 +90,16 @@ - (NSUInteger)mediaHash return self.hash; } +- (NSString *)mediaDataType +{ + return (NSString *)kUTTypeJPEG; +} + +- (id)mediaData +{ + return UIImageJPEGRepresentation(self.image, 1); +} + #pragma mark - NSObject - (NSUInteger)hash @@ -129,7 +134,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder - (instancetype)copyWithZone:(NSZone *)zone { - JSQPhotoMediaItem *copy = [[[self class] allocWithZone:zone] initWithImage:self.image]; + JSQPhotoMediaItem *copy = [[JSQPhotoMediaItem allocWithZone:zone] initWithImage:self.image]; copy.appliesMediaViewMaskAsOutgoing = self.appliesMediaViewMaskAsOutgoing; return copy; } diff --git a/JSQMessagesViewController/Model/JSQVideoMediaItem.h b/JSQMessagesViewController/Model/JSQVideoMediaItem.h index 362f41e2e..b6738db92 100644 --- a/JSQMessagesViewController/Model/JSQVideoMediaItem.h +++ b/JSQMessagesViewController/Model/JSQVideoMediaItem.h @@ -18,6 +18,8 @@ #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 @@ -29,11 +31,16 @@ /** * The URL that identifies a video resource. */ -@property (nonatomic, strong) NSURL *fileURL; +@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; @@ -44,13 +51,34 @@ * @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` if successful, `nil` otherwise. + * @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:(NSURL *)fileURL isReadyToPlay:(BOOL)isReadyToPlay; +- (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 diff --git a/JSQMessagesViewController/Model/JSQVideoMediaItem.m b/JSQMessagesViewController/Model/JSQVideoMediaItem.m index 8b06e4977..885fb748c 100644 --- a/JSQMessagesViewController/Model/JSQVideoMediaItem.m +++ b/JSQMessagesViewController/Model/JSQVideoMediaItem.m @@ -20,6 +20,7 @@ #import "JSQMessagesMediaPlaceholderView.h" #import "JSQMessagesMediaViewBubbleImageMasker.h" +#import "JSQMessagesVideoThumbnailFactory.h" #import "UIImage+JSQMessages.h" @@ -36,22 +37,22 @@ @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)dealloc -{ - _fileURL = nil; - _cachedVideoImageView = nil; -} - - (void)clearCachedMediaViews { [super clearCachedMediaViews]; @@ -85,20 +86,33 @@ - (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.backgroundColor = [UIColor blackColor]; imageView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height); imageView.contentMode = UIViewContentModeCenter; imageView.clipsToBounds = YES; [JSQMessagesMediaViewBubbleImageMasker applyBubbleImageMaskToMediaView:imageView isOutgoing:self.appliesMediaViewMaskAsOutgoing]; - self.cachedVideoImageView = imageView; + + 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; } @@ -114,11 +128,11 @@ - (BOOL)isEqual:(id)object if (![super isEqual:object]) { return NO; } - + JSQVideoMediaItem *videoItem = (JSQVideoMediaItem *)object; - + return [self.fileURL isEqual:videoItem.fileURL] - && self.isReadyToPlay == videoItem.isReadyToPlay; + && self.isReadyToPlay == videoItem.isReadyToPlay; } - (NSUInteger)hash @@ -128,8 +142,8 @@ - (NSUInteger)hash - (NSString *)description { - return [NSString stringWithFormat:@"<%@: fileURL=%@, isReadyToPlay=%@, appliesMediaViewMaskAsOutgoing=%@>", - [self class], self.fileURL, @(self.isReadyToPlay), @(self.appliesMediaViewMaskAsOutgoing)]; + return [NSString stringWithFormat:@"<%@: fileURL=%@, isReadyToPlay=%@, appliesMediaViewMaskAsOutgoing=%@>, thumbnailImage=%@", + [self class], self.fileURL, @(self.isReadyToPlay), @(self.appliesMediaViewMaskAsOutgoing), self.thumbnailImage]; } #pragma mark - NSCoding @@ -140,6 +154,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder if (self) { _fileURL = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(fileURL))]; _isReadyToPlay = [aDecoder decodeBoolForKey:NSStringFromSelector(@selector(isReadyToPlay))]; + _thumbnailImage = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(thumbnailImage))]; } return self; } @@ -149,6 +164,7 @@ - (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 @@ -156,7 +172,8 @@ - (void)encodeWithCoder:(NSCoder *)aCoder - (instancetype)copyWithZone:(NSZone *)zone { JSQVideoMediaItem *copy = [[[self class] allocWithZone:zone] initWithFileURL:self.fileURL - isReadyToPlay:self.isReadyToPlay]; + isReadyToPlay:self.isReadyToPlay + thumbnailImage:self.thumbnailImage]; copy.appliesMediaViewMaskAsOutgoing = self.appliesMediaViewMaskAsOutgoing; return copy; } diff --git a/JSQMessagesViewController/Views/JSQMessagesCellTextView.m b/JSQMessagesViewController/Views/JSQMessagesCellTextView.m index f95d1407b..ff8a5ec97 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCellTextView.m +++ b/JSQMessagesViewController/Views/JSQMessagesCellTextView.m @@ -44,10 +44,16 @@ - (void)awakeFromNib - (void)setSelectedRange:(NSRange)selectedRange { - // prevent selecting text + // 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 diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionView.h b/JSQMessagesViewController/Views/JSQMessagesCollectionView.h index b046cc7b5..db7d5d728 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionView.h +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionView.h @@ -25,7 +25,9 @@ @class JSQMessagesTypingIndicatorFooterView; @class JSQMessagesLoadEarlierHeaderView; +@protocol JSQMessagesViewAccessoryButtonDelegate; +NS_ASSUME_NONNULL_BEGIN /** * The `JSQMessagesCollectionView` class manages an ordered collection of message data items and presents @@ -37,13 +39,19 @@ * The object that provides the data for the collection view. * The data source must adopt the `JSQMessagesCollectionViewDataSource` protocol. */ -@property (weak, nonatomic) id dataSource; +@property (weak, nonatomic, nullable) id dataSource; /** * The object that acts as the delegate of the collection view. - * The delegate must adpot the `JSQMessagesCollectionViewDelegateFlowLayout` protocol. + * The delegate must adopt the `JSQMessagesCollectionViewDelegateFlowLayout` protocol. */ -@property (weak, nonatomic) id delegate; +@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. @@ -99,3 +107,5 @@ - (JSQMessagesLoadEarlierHeaderView *)dequeueLoadEarlierMessagesViewHeaderForIndexPath:(NSIndexPath *)indexPath; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionView.m b/JSQMessagesViewController/Views/JSQMessagesCollectionView.m index 527d2686c..70f94bdcf 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionView.m +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionView.m @@ -18,7 +18,8 @@ #import "JSQMessagesCollectionView.h" -#import "JSQMessagesCollectionViewFlowLayout.h" +#import "JSQMessagesViewAccessoryButtonDelegate.h" + #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" @@ -48,29 +49,29 @@ - (void)jsq_configureCollectionView [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.backgroundColor = [UIColor whiteColor]; - self.keyboardDismissMode = UIScrollViewKeyboardDismissModeNone; + self.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; self.alwaysBounceVertical = YES; self.bounces = YES; - + [self registerNib:[JSQMessagesCollectionViewCellIncoming nib] - forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellIncoming cellReuseIdentifier]]; - +forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellIncoming cellReuseIdentifier]]; + [self registerNib:[JSQMessagesCollectionViewCellOutgoing nib] - forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]]; - +forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]]; + [self registerNib:[JSQMessagesCollectionViewCellIncoming nib] - forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellIncoming mediaCellReuseIdentifier]]; - +forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellIncoming mediaCellReuseIdentifier]]; + [self registerNib:[JSQMessagesCollectionViewCellOutgoing nib] - forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellOutgoing mediaCellReuseIdentifier]]; - +forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellOutgoing mediaCellReuseIdentifier]]; + [self registerNib:[JSQMessagesTypingIndicatorFooterView nib] - forSupplementaryViewOfKind:UICollectionElementKindSectionFooter - withReuseIdentifier:[JSQMessagesTypingIndicatorFooterView footerReuseIdentifier]]; - +forSupplementaryViewOfKind:UICollectionElementKindSectionFooter + withReuseIdentifier:[JSQMessagesTypingIndicatorFooterView footerReuseIdentifier]]; + [self registerNib:[JSQMessagesLoadEarlierHeaderView nib] - forSupplementaryViewOfKind:UICollectionElementKindSectionHeader - withReuseIdentifier:[JSQMessagesLoadEarlierHeaderView headerReuseIdentifier]]; +forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:[JSQMessagesLoadEarlierHeaderView headerReuseIdentifier]]; _typingIndicatorDisplaysOnLeft = YES; _typingIndicatorMessageBubbleColor = [UIColor jsq_messageBubbleLightGrayColor]; @@ -104,6 +105,7 @@ - (JSQMessagesTypingIndicatorFooterView *)dequeueTypingIndicatorFooterViewForInd [footerView configureWithEllipsisColor:self.typingIndicatorEllipsisColor messageBubbleColor:self.typingIndicatorMessageBubbleColor + animated:YES shouldDisplayOnLeft:self.typingIndicatorDisplaysOnLeft forCollectionView:self]; @@ -182,4 +184,14 @@ - (void)messagesCollectionViewCell:(JSQMessagesCollectionViewCell *)cell didPerf withSender:sender]; } +- (void)messagesCollectionViewCellDidTapAccessoryButton:(JSQMessagesCollectionViewCell *)cell +{ + NSIndexPath *indexPath = [self indexPathForCell:cell]; + if (indexPath == nil) { + return; + } + + [self.accessoryDelegate messageView:self didTapAccessoryButtonAtIndexPath:indexPath]; +} + @end diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h index c1124cb1b..2a27b7c4f 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h @@ -23,6 +23,8 @@ @class JSQMessagesCollectionViewCell; +NS_ASSUME_NONNULL_BEGIN + /** * The `JSQMessagesCollectionViewCellDelegate` protocol defines methods that allow you to manage * additional interactions within the collection view cell. @@ -72,6 +74,13 @@ */ - (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 @@ -90,39 +99,39 @@ /** * The object that acts as the delegate for the cell. */ -@property (weak, nonatomic) id delegate; +@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) JSQMessagesLabel *cellTopLabel; +@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) JSQMessagesLabel *messageBubbleTopLabel; +@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) JSQMessagesLabel *cellBottomLabel; +@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) JSQMessagesCellTextView *textView; +@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) UIImageView *messageBubbleImageView; +@property (weak, nonatomic, readonly, nullable) UIImageView *messageBubbleImageView; /** * Returns the message bubble container view of the cell. This view is the superview of @@ -135,12 +144,12 @@ * its frame, nor should you remove this view from the cell or remove any of its subviews. * Doing so could result in unexpected behavior. */ -@property (weak, nonatomic, readonly) UIView *messageBubbleContainerView; +@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) UIImageView *avatarImageView; +@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. @@ -152,28 +161,32 @@ * its frame, nor should you remove this view from the cell or remove any of its subviews. * Doing so could result in unexpected behavior. */ -@property (weak, nonatomic, readonly) UIView *avatarContainerView; +@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) UIView *mediaView; +@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) UITapGestureRecognizer *tapGestureRecognizer; +@property (weak, nonatomic, readonly, nullable) UITapGestureRecognizer *tapGestureRecognizer; #pragma mark - Class methods /** * Returns the `UINib` object initialized for the cell. * - * @return The initialized `UINib` object or `nil` if there were errors during - * initialization or the nib file could not be located. + * @return The initialized `UINib` object. */ + (UINib *)nib; @@ -204,3 +217,5 @@ + (void)registerMenuAction:(SEL)action; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m index 3ea4896ad..c5660025c 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m @@ -23,7 +23,7 @@ #import "JSQMessagesCollectionViewLayoutAttributes.h" #import "UIView+JSQMessages.h" -#import "UIDevice+JSQMessages.h" +#import "UIImage+JSQMessages.h" static NSMutableSet *jsqMessagesCollectionViewCellActions = nil; @@ -42,6 +42,8 @@ @interface JSQMessagesCollectionViewCell () @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; @@ -101,6 +103,7 @@ + (void)registerMenuAction:(SEL)action [jsqMessagesCollectionViewCellActions addObject:NSStringFromSelector(action)]; } + #pragma mark - Initialization - (void)awakeFromNib @@ -109,29 +112,45 @@ - (void)awakeFromNib [self setTranslatesAutoresizingMaskIntoConstraints:NO]; + self.isAccessibilityElement = YES; + self.backgroundColor = [UIColor whiteColor]; - - self.cellTopLabelHeightConstraint.constant = 0.0f; - self.messageBubbleTopLabelHeightConstraint.constant = 0.0f; - self.cellBottomLabelHeightConstraint.constant = 0.0f; - self.avatarViewSize = CGSizeZero; - + + UIFont *topLabelFont = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; self.cellTopLabel.textAlignment = NSTextAlignmentCenter; - self.cellTopLabel.font = [UIFont boldSystemFontOfSize:12.0f]; + self.cellTopLabel.font = topLabelFont; self.cellTopLabel.textColor = [UIColor lightGrayColor]; - - self.messageBubbleTopLabel.font = [UIFont systemFontOfSize:12.0f]; + self.cellTopLabel.numberOfLines = 0; + + UIFont *messageBubbleTopLabelFont = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; + self.messageBubbleTopLabel.font = messageBubbleTopLabelFont; self.messageBubbleTopLabel.textColor = [UIColor lightGrayColor]; - - self.cellBottomLabel.font = [UIFont systemFontOfSize:11.0f]; + 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; @@ -166,6 +185,8 @@ - (void)prepareForReuse self.avatarImageView.image = nil; self.avatarImageView.highlightedImage = nil; + + self.accessoryButton.hidden = YES; } - (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes @@ -212,29 +233,17 @@ - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttribut - (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; } -// FIXME: radar 18326340 -// remove when fixed -// hack for Xcode6 / iOS 8 SDK rendering bug that occurs on iOS 7.x -// see issue #484 -// https://github.com/jessesquires/JSQMessagesViewController/issues/484 -// -- (void)setBounds:(CGRect)bounds -{ - [super setBounds:bounds]; - - if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) { - self.contentView.frame = bounds; - } -} #pragma mark - Menu actions @@ -247,10 +256,26 @@ - (BOOL)respondsToSelector:(SEL)aSelector 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)]) { - id sender; + __unsafe_unretained id sender; [anInvocation getArgument:&sender atIndex:0]; [self.delegate messagesCollectionViewCell:self didPerformAction:anInvocation.selector withSender:sender]; } @@ -323,11 +348,11 @@ - (void)setMediaView:(UIView *)mediaView // 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(), ^{ - for (NSUInteger i = 0; i < self.messageBubbleContainerView.subviews.count; i++) { - if (self.messageBubbleContainerView.subviews[i] != _mediaView) { - [self.messageBubbleContainerView.subviews[i] removeFromSuperview]; + [self.messageBubbleContainerView.subviews enumerateObjectsUsingBlock:^(UIView *subview, NSUInteger index, BOOL *stop) { + if (subview != _mediaView) { + [subview removeFromSuperview]; } - } + }]; }); } @@ -383,7 +408,12 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive return CGRectContainsPoint(self.messageBubbleContainerView.frame, touchPt); } - return YES; + return NO; +} + +- (IBAction)didTapAccessoryButton:(UIButton *)accessoryButton +{ + [self.delegate messagesCollectionViewCellDidTapAccessoryButton:self]; } @end diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib index 314ec0e12..7c6b119fb 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib @@ -1,8 +1,8 @@ - + - + @@ -21,7 +21,7 @@ - + @@ -40,9 +40,9 @@ - + - + @@ -84,9 +84,19 @@ - + + @@ -94,6 +104,8 @@ + + @@ -108,6 +120,7 @@ + @@ -130,9 +143,4 @@ - - - - - diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib index 3b1bd35b9..a345b27d0 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib @@ -1,8 +1,8 @@ - + - + @@ -21,7 +21,7 @@ - + @@ -40,9 +40,9 @@ - + - + @@ -84,9 +84,19 @@ - + + @@ -103,11 +113,14 @@ + + + @@ -127,12 +140,7 @@ - + - - - - - diff --git a/JSQMessagesViewController/Views/JSQMessagesComposerTextView.h b/JSQMessagesViewController/Views/JSQMessagesComposerTextView.h index 5dc99cd17..496fcc1a7 100644 --- a/JSQMessagesViewController/Views/JSQMessagesComposerTextView.h +++ b/JSQMessagesViewController/Views/JSQMessagesComposerTextView.h @@ -18,6 +18,26 @@ #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`. @@ -27,13 +47,23 @@ /** * The text to be displayed when the text view is empty. The default value is `nil`. */ -@property (copy, nonatomic) NSString *placeHolder; +@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. @@ -43,3 +73,5 @@ - (BOOL)hasText; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Views/JSQMessagesComposerTextView.m b/JSQMessagesViewController/Views/JSQMessagesComposerTextView.m index 21d93f7b4..94e5ff0e7 100644 --- a/JSQMessagesViewController/Views/JSQMessagesComposerTextView.m +++ b/JSQMessagesViewController/Views/JSQMessagesComposerTextView.m @@ -22,21 +22,14 @@ #import "NSString+JSQMessages.h" - @interface JSQMessagesComposerTextView () -- (void)jsq_configureTextView; - -- (void)jsq_addTextViewNotificationObservers; -- (void)jsq_removeTextViewNotificationObservers; -- (void)jsq_didReceiveTextViewNotification:(NSNotification *)notification; - -- (NSDictionary *)jsq_placeholderTextAttributes; +@property (nonatomic, weak) NSLayoutConstraint *heightConstraint; +@property (nonatomic, weak) NSLayoutConstraint *minHeightConstraint; +@property (nonatomic, weak) NSLayoutConstraint *maxHeightConstraint; @end - - @implementation JSQMessagesComposerTextView #pragma mark - Initialization @@ -44,38 +37,41 @@ @implementation JSQMessagesComposerTextView - (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 systemFontOfSize:16.0f]; + + 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]; } @@ -97,8 +93,52 @@ - (void)awakeFromNib - (void)dealloc { [self jsq_removeTextViewNotificationObservers]; - _placeHolder = nil; - _placeHolderTextColor = nil; +} + +// 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 @@ -115,7 +155,7 @@ - (void)setPlaceHolder:(NSString *)placeHolder if ([placeHolder isEqualToString:_placeHolder]) { return; } - + _placeHolder = [placeHolder copy]; [self setNeedsDisplay]; } @@ -125,13 +165,33 @@ - (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]; @@ -156,16 +216,23 @@ - (void)setTextAlignment:(NSTextAlignment)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:CGRectInset(rect, 7.0f, 5.0f) + [self.placeHolder drawInRect:UIEdgeInsetsInsetRect(rect, self.placeHolderInsets) withAttributes:[self jsq_placeholderTextAttributes]]; } } @@ -178,12 +245,12 @@ - (void)jsq_addTextViewNotificationObservers 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 @@ -195,11 +262,11 @@ - (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]; @@ -217,10 +284,27 @@ - (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 diff --git a/JSQMessagesViewController/Views/JSQMessagesInputToolbar.h b/JSQMessagesViewController/Views/JSQMessagesInputToolbar.h index c41519188..d7de63bd8 100644 --- a/JSQMessagesViewController/Views/JSQMessagesInputToolbar.h +++ b/JSQMessagesViewController/Views/JSQMessagesInputToolbar.h @@ -23,6 +23,14 @@ @class JSQMessagesInputToolbar; +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, JSQMessagesInputSendButtonLocation) { + JSQMessagesInputSendButtonLocationNone, + JSQMessagesInputSendButtonLocationRight, + JSQMessagesInputSendButtonLocationLeft +}; + /** * The `JSQMessagesInputToolbarDelegate` protocol defines methods for interacting with @@ -55,52 +63,61 @@ /** * An instance of `JSQMessagesInputToolbar` defines the input toolbar for - * composing a new message. It is displayed above and follow the movement of - * the system keyboard. + * composing a new message. It is displayed above and follow the movement of the system keyboard. */ @interface JSQMessagesInputToolbar : UIToolbar /** * The object that acts as the delegate of the toolbar. */ -@property (weak, nonatomic) id delegate; +@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) JSQMessagesToolbarContentView *contentView; +@property (weak, nonatomic, readonly, nullable) JSQMessagesToolbarContentView *contentView; /** - * A boolean value indicating whether the send button is on the right side of the toolbar or not. - * - * @discussion The default value is `YES`, which indicates that the send button is the right-most subview of - * the toolbar's `contentView`. Set to `NO` to specify that the send button is on the left. This + * 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. + * 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) BOOL sendButtonOnRight; +@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 height for the toolbar. The default value is `44.0f`. This value must be positive. + * Specifies the default (minimum) height for the toolbar. The default value is `44.0f`. This value must be positive. */ @property (assign, nonatomic) CGFloat preferredDefaultHeight; /** - * Enables or disables the send button based on whether or not its `textView` has text. - * That is, the send button will be enabled if there is text in the `textView`, and disabled otherwise. + * Specifies the maximum height for the toolbar. The default value is `NSNotFound`, which specifies no maximum height. */ -- (void)toggleSendButtonEnabled; +@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` if successful, otherwise `nil`. + * @return An initialized `JSQMessagesToolbarContentView`. */ - (JSQMessagesToolbarContentView *)loadToolbarContentView; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Views/JSQMessagesInputToolbar.m b/JSQMessagesViewController/Views/JSQMessagesInputToolbar.m index 76d0bfc00..85cf3f222 100644 --- a/JSQMessagesViewController/Views/JSQMessagesInputToolbar.m +++ b/JSQMessagesViewController/Views/JSQMessagesInputToolbar.m @@ -33,12 +33,6 @@ @interface JSQMessagesInputToolbar () @property (assign, nonatomic) BOOL jsq_isObserving; -- (void)jsq_leftBarButtonPressed:(UIButton *)sender; -- (void)jsq_rightBarButtonPressed:(UIButton *)sender; - -- (void)jsq_addObservers; -- (void)jsq_removeObservers; - @end @@ -52,12 +46,13 @@ @implementation JSQMessagesInputToolbar - (void)awakeFromNib { [super awakeFromNib]; - [self setTranslatesAutoresizingMaskIntoConstraints:NO]; - + self.backgroundColor = [UIColor whiteColor]; self.jsq_isObserving = NO; - self.sendButtonOnRight = YES; + self.sendButtonLocation = JSQMessagesInputSendButtonLocationRight; + self.enablesSendButtonAutomatically = YES; self.preferredDefaultHeight = 44.0f; + self.maximumHeight = NSNotFound; JSQMessagesToolbarContentView *toolbarContentView = [self loadToolbarContentView]; toolbarContentView.frame = self.frame; @@ -68,10 +63,16 @@ - (void)awakeFromNib [self jsq_addObservers]; - self.contentView.leftBarButtonItem = [JSQMessagesToolbarButtonFactory defaultAccessoryButtonItem]; - self.contentView.rightBarButtonItem = [JSQMessagesToolbarButtonFactory defaultSendButtonItem]; + JSQMessagesToolbarButtonFactory *toolbarButtonFactory = [[JSQMessagesToolbarButtonFactory alloc] initWithFont:[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]]; + self.contentView.leftBarButtonItem = [toolbarButtonFactory defaultAccessoryButtonItem]; + self.contentView.rightBarButtonItem = [toolbarButtonFactory defaultSendButtonItem]; + + [self updateSendButtonEnabledState]; - [self toggleSendButtonEnabled]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(textViewTextDidChangeNotification:) + name:UITextViewTextDidChangeNotification + object:_contentView.textView]; } - (JSQMessagesToolbarContentView *)loadToolbarContentView @@ -85,7 +86,7 @@ - (JSQMessagesToolbarContentView *)loadToolbarContentView - (void)dealloc { [self jsq_removeObservers]; - _contentView = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - Setters @@ -96,6 +97,12 @@ - (void)setPreferredDefaultHeight:(CGFloat)preferredDefaultHeight _preferredDefaultHeight = preferredDefaultHeight; } +- (void)setEnablesSendButtonAutomatically:(BOOL)enablesSendButtonAutomatically +{ + _enablesSendButtonAutomatically = enablesSendButtonAutomatically; + [self updateSendButtonEnabledState]; +} + #pragma mark - Actions - (void)jsq_leftBarButtonPressed:(UIButton *)sender @@ -110,18 +117,32 @@ - (void)jsq_rightBarButtonPressed:(UIButton *)sender #pragma mark - Input toolbar -- (void)toggleSendButtonEnabled +- (void)updateSendButtonEnabledState { - BOOL hasText = [self.contentView.textView hasText]; - - if (self.sendButtonOnRight) { - self.contentView.rightBarButtonItem.enabled = hasText; + if (!self.enablesSendButtonAutomatically) { + return; } - else { - self.contentView.leftBarButtonItem.enabled = hasText; + + 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 @@ -150,7 +171,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N forControlEvents:UIControlEventTouchUpInside]; } - [self toggleSendButtonEnabled]; + [self updateSendButtonEnabledState]; } } } diff --git a/JSQMessagesViewController/Views/JSQMessagesLabel.m b/JSQMessagesViewController/Views/JSQMessagesLabel.m index 1ced49ca2..09921054d 100644 --- a/JSQMessagesViewController/Views/JSQMessagesLabel.m +++ b/JSQMessagesViewController/Views/JSQMessagesLabel.m @@ -18,14 +18,6 @@ #import "JSQMessagesLabel.h" - -@interface JSQMessagesLabel () - -- (void)jsq_configureLabel; - -@end - - @implementation JSQMessagesLabel #pragma mark - Initialization diff --git a/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.h b/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.h index 1bf3c998e..f33582c5d 100644 --- a/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.h +++ b/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.h @@ -25,6 +25,8 @@ */ FOUNDATION_EXPORT const CGFloat kJSQMessagesLoadEarlierHeaderViewHeight; +NS_ASSUME_NONNULL_BEGIN + /** * The `JSQMessagesLoadEarlierHeaderViewDelegate` defines methods that allow you to * respond to interactions within the header view. @@ -54,20 +56,19 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesLoadEarlierHeaderViewHeight; /** * The object that acts as the delegate of the header view. */ -@property (weak, nonatomic) id delegate; +@property (weak, nonatomic, nullable) id delegate; /** * Returns the load button of the header view. */ -@property (weak, nonatomic, readonly) UIButton *loadButton; +@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 or `nil` if there were errors during - * initialization or the nib file could not be located. + * @return The initialized `UINib` object. */ + (UINib *)nib; @@ -79,3 +80,5 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesLoadEarlierHeaderViewHeight; + (NSString *)headerReuseIdentifier; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.m b/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.m index 903ba6b9d..f88ebab63 100644 --- a/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.m +++ b/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.m @@ -29,8 +29,6 @@ @interface JSQMessagesLoadEarlierHeaderView () @property (weak, nonatomic) IBOutlet UIButton *loadButton; -- (IBAction)loadButtonPressed:(UIButton *)sender; - @end diff --git a/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.xib b/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.xib index b650e57b6..e705e4609 100644 --- a/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.xib +++ b/JSQMessagesViewController/Views/JSQMessagesLoadEarlierHeaderView.xib @@ -1,7 +1,8 @@ - + - + + @@ -31,9 +32,4 @@ - - - - - diff --git a/JSQMessagesViewController/Views/JSQMessagesMediaPlaceholderView.h b/JSQMessagesViewController/Views/JSQMessagesMediaPlaceholderView.h index 81425f292..697ba862a 100644 --- a/JSQMessagesViewController/Views/JSQMessagesMediaPlaceholderView.h +++ b/JSQMessagesViewController/Views/JSQMessagesMediaPlaceholderView.h @@ -19,6 +19,8 @@ #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. @@ -34,12 +36,12 @@ /** * Returns the activity indicator view for this placeholder view, or `nil` if it does not exist. */ -@property (nonatomic, weak, readonly) UIActivityIndicatorView *activityIndicatorView; +@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) UIImageView *imageView; +@property (nonatomic, weak, readonly, nullable) UIImageView *imageView; /** * Creates a media placeholder view object with a light gray background and @@ -48,7 +50,7 @@ * @discussion When initializing a `JSQMessagesMediaPlaceholderView` with this method, * its imageView property will be nil. * - * @return An initialized `JSQMessagesMediaPlaceholderView` object if successful, `nil` otherwise. + * @return An initialized `JSQMessagesMediaPlaceholderView` object. */ + (instancetype)viewWithActivityIndicator; @@ -59,7 +61,7 @@ * @discussion When initializing a `JSQMessagesMediaPlaceholderView` with this method, * its activityIndicatorView property will be nil. * - * @return An initialized `JSQMessagesMediaPlaceholderView` object if successful, `nil` otherwise. + * @return An initialized `JSQMessagesMediaPlaceholderView` object. */ + (instancetype)viewWithAttachmentIcon; @@ -70,7 +72,7 @@ * @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 if successful, `nil` otherwise. + * @return An initialized `JSQMessagesMediaPlaceholderView` object. */ - (instancetype)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor @@ -83,10 +85,12 @@ * @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 if successful, `nil` otherwise. + * @return An initialized `JSQMessagesMediaPlaceholderView` object. */ - (instancetype)initWithFrame:(CGRect)frame - backgroundColor:(UIColor *)backgroundColor - imageView:(UIImageView *)imageView; + backgroundColor:(UIColor *)backgroundColor + imageView:(UIImageView *)imageView; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.h b/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.h index 08939d10f..755fbb66b 100644 --- a/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.h +++ b/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.h @@ -21,6 +21,8 @@ #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. @@ -37,7 +39,7 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingD /** * Returns the text view in which the user composes a message. */ -@property (weak, nonatomic, readonly) JSQMessagesComposerTextView *textView; +@property (weak, nonatomic, readonly, nullable) JSQMessagesComposerTextView *textView; /** * A custom button item displayed on the left of the toolbar content view. @@ -49,7 +51,7 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingD * 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) UIButton *leftBarButtonItem; +@property (weak, nonatomic, nullable) UIButton *leftBarButtonItem; /** * Specifies the width of the leftBarButtonItem. @@ -58,6 +60,13 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingD */ @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. * @@ -66,7 +75,7 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingD * However, you will be completely responsible for responding to all touch events for these buttons * in your `JSQMessagesViewController` subclass. */ -@property (weak, nonatomic, readonly) UIView *leftBarButtonContainerView; +@property (weak, nonatomic, readonly, nullable) UIView *leftBarButtonContainerView; /** * A custom button item displayed on the right of the toolbar content view. @@ -78,7 +87,7 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingD * 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) UIButton *rightBarButtonItem; +@property (weak, nonatomic, nullable) UIButton *rightBarButtonItem; /** * Specifies the width of the rightBarButtonItem. @@ -87,6 +96,13 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingD */ @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. * @@ -95,16 +111,17 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesToolbarContentViewHorizontalSpacingD * However, you will be completely responsible for responding to all touch events for these buttons * in your `JSQMessagesViewController` subclass. */ -@property (weak, nonatomic, readonly) UIView *rightBarButtonContainerView; +@property (weak, nonatomic, readonly, nullable) UIView *rightBarButtonContainerView; #pragma mark - Class methods /** * Returns the `UINib` object initialized for a `JSQMessagesToolbarContentView`. * - * @return The initialized `UINib` object or `nil` if there were errors during - * initialization or the nib file could not be located. + * @return The initialized `UINib` object. */ + (UINib *)nib; @end + +NS_ASSUME_NONNULL_END diff --git a/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.m b/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.m index 9913a804b..1a7bb48a6 100644 --- a/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.m +++ b/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.m @@ -55,7 +55,7 @@ + (UINib *)nib - (void)awakeFromNib { [super awakeFromNib]; - + [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.leftHorizontalSpacingConstraint.constant = kJSQMessagesToolbarContentViewHorizontalSpacingDefault; @@ -64,15 +64,6 @@ - (void)awakeFromNib self.backgroundColor = [UIColor clearColor]; } -- (void)dealloc -{ - _textView = nil; - _leftBarButtonItem = nil; - _rightBarButtonItem = nil; - _leftBarButtonContainerView = nil; - _rightBarButtonContainerView = nil; -} - #pragma mark - Setters - (void)setBackgroundColor:(UIColor *)backgroundColor @@ -156,6 +147,18 @@ - (void)setRightBarButtonItemWidth:(CGFloat)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 @@ -168,6 +171,16 @@ - (CGFloat)rightBarButtonItemWidth return self.rightBarButtonContainerViewWidthConstraint.constant; } +- (CGFloat)rightContentPadding +{ + return self.rightHorizontalSpacingConstraint.constant; +} + +- (CGFloat)leftContentPadding +{ + return self.leftHorizontalSpacingConstraint.constant; +} + #pragma mark - UIView overrides - (void)setNeedsDisplay diff --git a/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.xib b/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.xib index ab7b3f3df..4ce7d003c 100644 --- a/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.xib +++ b/JSQMessagesViewController/Views/JSQMessagesToolbarContentView.xib @@ -1,8 +1,8 @@ - + - + @@ -27,9 +27,14 @@ - + + + + + + @@ -59,9 +64,4 @@ - - - - - diff --git a/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.h b/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.h index dcd1bb829..54224e134 100644 --- a/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.h +++ b/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.h @@ -24,6 +24,8 @@ */ 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 @@ -36,8 +38,7 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesTypingIndicatorFooterViewHeight; /** * Returns the `UINib` object initialized for the collection reusable view. * - * @return The initialized `UINib` object or `nil` if there were errors during - * initialization or the nib file could not be located. + * @return The initialized `UINib` object. */ + (UINib *)nib; @@ -51,17 +52,20 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesTypingIndicatorFooterViewHeight; #pragma mark - Typing indicator /** - * Configures the receiver with the specified attributes for the given collection view. + * 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 diff --git a/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.m b/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.m index 098d7e2d4..f18a11f10 100644 --- a/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.m +++ b/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.m @@ -19,8 +19,10 @@ #import "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesBubbleImageFactory.h" +#import "JSQMessagesTypingView.h" #import "UIImage+JSQMessages.h" +#import "UIColor+JSQMessages.h" const CGFloat kJSQMessagesTypingIndicatorFooterViewHeight = 46.0f; @@ -29,14 +31,12 @@ @interface JSQMessagesTypingIndicatorFooterView () @property (weak, nonatomic) IBOutlet UIImageView *bubbleImageView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bubbleImageViewRightHorizontalConstraint; - -@property (weak, nonatomic) IBOutlet UIImageView *typingIndicatorImageView; +@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 @@ -60,13 +60,6 @@ - (void)awakeFromNib [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.backgroundColor = [UIColor clearColor]; self.userInteractionEnabled = NO; - self.typingIndicatorImageView.contentMode = UIViewContentModeScaleAspectFit; -} - -- (void)dealloc -{ - _bubbleImageView = nil; - _typingIndicatorImageView = nil; } #pragma mark - Reusable view @@ -81,41 +74,63 @@ - (void)setBackgroundColor:(UIColor *)backgroundColor - (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; - CGFloat indicatorMarginMinimumSpacing = 26.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 indicatorWidth = CGRectGetWidth(self.typingIndicatorImageView.frame); - CGFloat bubbleMarginMaximumSpacing = collectionViewWidth - bubbleWidth - bubbleMarginMinimumSpacing; - CGFloat indicatorMarginMaximumSpacing = collectionViewWidth - indicatorWidth - indicatorMarginMinimumSpacing; - + self.bubbleImageViewRightHorizontalConstraint.constant = bubbleMarginMaximumSpacing; - self.typingIndicatorImageViewRightHorizontalConstraint.constant = indicatorMarginMaximumSpacing; + self.typingIndicatorToBubbleImageAlignConstraint.constant = 0; } else { self.bubbleImageView.image = [bubbleImageFactory outgoingMessagesBubbleImageWithColor:messageBubbleColor].messageBubbleImage; - self.bubbleImageViewRightHorizontalConstraint.constant = bubbleMarginMinimumSpacing; - self.typingIndicatorImageViewRightHorizontalConstraint.constant = indicatorMarginMinimumSpacing; + self.typingIndicatorToBubbleImageAlignConstraint.constant = 6; } - + [self setNeedsUpdateConstraints]; - - self.typingIndicatorImageView.image = [[UIImage jsq_defaultTypingIndicatorImage] jsq_imageMaskedWithColor:ellipsisColor]; + + 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 diff --git a/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.xib b/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.xib index a5e4c2edf..7d942d0cf 100644 --- a/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.xib +++ b/JSQMessagesViewController/Views/JSQMessagesTypingIndicatorFooterView.xib @@ -1,7 +1,8 @@ - + - + + @@ -17,31 +18,27 @@ - - + + + - - + + - + - + - + - - + + - - - - - diff --git a/JSQMessagesViewController/Views/JSQMessagesTypingView.h b/JSQMessagesViewController/Views/JSQMessagesTypingView.h new file mode 100644 index 000000000..a90ed610f --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesTypingView.h @@ -0,0 +1,40 @@ +// +// 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 diff --git a/JSQMessagesViewController/Views/JSQMessagesTypingView.m b/JSQMessagesViewController/Views/JSQMessagesTypingView.m new file mode 100644 index 000000000..80616057f --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesTypingView.m @@ -0,0 +1,134 @@ +// +// 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 diff --git a/LICENSE b/LICENSE index fcef34e8c..e7a4b01d5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,8 @@ MIT License -Copyright (c) 2014 Jesse Squires +Copyright (c) 2013-present Jesse Squires -http://www.hexedbits.com +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 diff --git a/Podfile b/Podfile deleted file mode 100644 index 25c656b35..000000000 --- a/Podfile +++ /dev/null @@ -1,12 +0,0 @@ -source 'https://github.com/CocoaPods/Specs.git' - -platform :ios, '7.0' - -# ignore all warnings from all pods -inhibit_all_warnings! - -pod 'JSQSystemSoundPlayer', '~> 2.0' - -target :JSQMessagesTests, :exclusive => true do - pod 'OCMock' -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index c96027af8..000000000 --- a/Podfile.lock +++ /dev/null @@ -1,13 +0,0 @@ -PODS: - - JSQSystemSoundPlayer (2.0.1) - - OCMock (3.1.2) - -DEPENDENCIES: - - JSQSystemSoundPlayer (~> 2.0) - - OCMock - -SPEC CHECKSUMS: - JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d - OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92 - -COCOAPODS: 0.37.0 diff --git a/README.md b/README.md index b50d800b7..db4e521c0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ +[![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)](http://travis-ci.org/jessesquires/JSQMessagesViewController) [![Version Status](http://img.shields.io/cocoapods/v/JSQMessagesViewController.png)][docsLink] [![license MIT](http://img.shields.io/badge/license-MIT-orange.png)][mitLink] +[![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]    +------------------------ + +![Screenshot0][img0]    ![Screenshot1][img1]    ![Screenshot2][img2]    ![Screenshot3][img3] @@ -10,7 +19,13 @@ ## Features -See the [website](http://www.jessesquires.com/JSQMessagesViewController/) for the list of 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 @@ -23,231 +38,68 @@ See the [website](http://www.jessesquires.com/JSQMessagesViewController/) for th ## Installation -From [CocoaPods](http://cocoapods.org): +### [CocoaPods](https://cocoapods.org/) (recommended) ````ruby # For latest release in cocoapods -pod 'JSQMessagesViewController' +pod 'JSQMessagesViewController' -# Feeling adventurous? Get the latest on develop +# Latest on develop pod 'JSQMessagesViewController', :git => 'https://github.com/jessesquires/JSQMessagesViewController.git', :branch => 'develop' - -# For version 5.3.2 -pod 'JSQMessagesViewController', :git => 'https://github.com/jessesquires/JSQMessagesViewController', :branch => 'version_5.3.2_patch' ```` -Without CocoaPods: - -1. *Why aren't you using CocoaPods?* -2. Drag the `JSQMessagesViewController/` folder to your project and install [`JSQSystemSoundPlayer`][playerLink]. - ->**NOTE:** -> ->This repo was formerly named `MessagesTableViewController`. -> ->And this pod was formerly named `JSMessagesViewController`. - ## Getting Started -````objective-c -#import // import all the things -```` - ->Read the [blog post](http://www.jessesquires.com/introducing-jsqmessagesvc-6-0/) about the 6.0 release! - -* **Demo Project** - * There's a sweet demo project: `JSQMessages.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. - -## Quick Tips - -##### *Springy bubbles?* -````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; -} -```` - -##### *Customize your cells?* -````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; -} -```` - -##### *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; -} -```` +See the [Getting Started](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/getting_started.md) guide! ## Questions & Help -There's [a label](https://github.com/jessesquires/JSQMessagesViewController/labels/questions%20&%20help) for that. Before asking a question, see if it has [already been answered](https://github.com/jessesquires/JSQMessagesViewController/issues?q=label%3A%22questions+%26+help%22+is%3Aclosed). **Please only ask questions that are _specific_ to this library.** - ->**NOTE:** Please try to avoid emailing me with questions. I prefer to keep questions and their answers open-source. - -## Migrating between major versions - -##### 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. - -##### 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. +* 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 fucking docs, [available here][docsLink] via [@CocoaDocs](https://twitter.com/CocoaDocs). +Read the docs, [available here][docsLink] via [@CocoaDocs](https://twitter.com/CocoaDocs). -## Contribute +## Core team -Please follow these sweet [contribution guidelines](https://github.com/jessesquires/HowToContribute). +- 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)) -## Donate +## Contributing -Support the development of this **free** *open-source* library! +Please follow these sweet [contribution guidelines](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/CONTRIBUTING.md). ->*Donations made via [Square Cash](https://square.com/cash)* - ->

Send $5 Just saying thanks. Here's a coffee! :coffee:

-

Send $10 This library is great. Lunch is on me! :ramen:

-

Send $25 This totally saved me time. Go get a nice dinner! :fork_and_knife:

-

Send $50 I love this library. I want new features! :clap:

-

Send $100 I really want to support this project! :tada:

->*You can also send donations via [PayPal](https://www.paypal.com) to jesse.squires.developer@gmail.com* +> **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 by [**@jesse_squires**](https://twitter.com/jesse_squires), a [programming-motherfucker](http://programming-motherfucker.com). - -* Assets extracted using [**@0xced**](https://github.com/0xced) / [iOS-Artwork-Extractor](https://github.com/0xced/iOS-Artwork-Extractor). -* Originally inspired by [**@soffes**](http://github.com/soffes) / [SSMessagingViewController](https://github.com/soffes/ssmessagesviewcontroller). +* 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. - -## About - -I initially developed this library to use in [Hemoglobe](http://bit.ly/hmglb) for private messages between users. - -As it turns out, messaging is something that iOS devs and users really want. Messaging of any kind has turned out to be an increasingly popular mobile app feature in all sorts of contexts and for all sorts of reasons. Thus, I am supporting this project in my free time and have added features way beyond what Hemoglobe ever needed. - -Feel free to read [my blog](http://bit.ly/jsqsf) and check out my work at [Hexed Bits](http://bit.ly/0x29A). +* iOS assets extracted using [**@0xced**](https://github.com/0xced) / [iOS-Artwork-Extractor](https://github.com/0xced/iOS-Artwork-Extractor). ## Apps using this library -* [Hemoglobe](http://bit.ly/hemoglobeapp) -* [PocketSuite](https://itunes.apple.com/us/app/pocketsuite/id721795146) -* [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](http://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/AwesomeChat) -* [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/NotificationChat) -* [RealtimeChat](https://github.com/relatedcode/RealtimeChat) -* [Bazar](https://itunes.apple.com/ru/app/bazar-talk-about-everything/id885453058) -* *Your app here* +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 © 2014 Jesse Squires.** +>**Copyright © 2013-present Jesse Squires.** *Please provide attribution, it is greatly appreciated.* -[docsLink]:http://cocoadocs.org/docsets/JSQMessagesViewController +[docsLink]:http://cocoadocs.org/docsets/JSQMessagesViewController/ +[podLink]:https://cocoapods.org/pods/JSQMessagesViewController [mitLink]:http://opensource.org/licenses/MIT [playerLink]:https://github.com/jessesquires/JSQSystemSoundPlayer diff --git a/Screenshots/screenshot0.png b/Screenshots/screenshot0.png index eea3a2098..77b8500e1 100644 Binary files a/Screenshots/screenshot0.png and b/Screenshots/screenshot0.png differ diff --git a/Screenshots/screenshot1.png b/Screenshots/screenshot1.png index a834cd7f4..e8741abef 100644 Binary files a/Screenshots/screenshot1.png and b/Screenshots/screenshot1.png differ diff --git a/Screenshots/screenshot2.png b/Screenshots/screenshot2.png index 7b94a215f..f77748bb7 100644 Binary files a/Screenshots/screenshot2.png and b/Screenshots/screenshot2.png differ diff --git a/Screenshots/screenshot3.png b/Screenshots/screenshot3.png index 4f7ead650..91c35ea8b 100644 Binary files a/Screenshots/screenshot3.png and b/Screenshots/screenshot3.png differ diff --git a/SwiftExample/Podfile b/SwiftExample/Podfile new file mode 100644 index 000000000..5fac9d1fb --- /dev/null +++ b/SwiftExample/Podfile @@ -0,0 +1,13 @@ +# 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 diff --git a/SwiftExample/Podfile.lock b/SwiftExample/Podfile.lock new file mode 100644 index 000000000..57c470caa --- /dev/null +++ b/SwiftExample/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - JSQMessagesViewController (7.3.4) + +DEPENDENCIES: + - JSQMessagesViewController (from `../`) + +EXTERNAL SOURCES: + JSQMessagesViewController: + :path: ../ + +SPEC CHECKSUMS: + JSQMessagesViewController: c11b9e77372ab72c45c67311f6da7342e32df1e8 + +PODFILE CHECKSUM: b51479be92e08cc1504a6937b0ded5693c7d1bd9 + +COCOAPODS: 1.0.1 diff --git a/SwiftExample/Pods/Local Podspecs/JSQMessagesViewController.podspec.json b/SwiftExample/Pods/Local Podspecs/JSQMessagesViewController.podspec.json new file mode 100644 index 000000000..c1814cb1a --- /dev/null +++ b/SwiftExample/Pods/Local Podspecs/JSQMessagesViewController.podspec.json @@ -0,0 +1,36 @@ +{ + "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 +} diff --git a/SwiftExample/Pods/Manifest.lock b/SwiftExample/Pods/Manifest.lock new file mode 100644 index 000000000..57c470caa --- /dev/null +++ b/SwiftExample/Pods/Manifest.lock @@ -0,0 +1,16 @@ +PODS: + - JSQMessagesViewController (7.3.4) + +DEPENDENCIES: + - JSQMessagesViewController (from `../`) + +EXTERNAL SOURCES: + JSQMessagesViewController: + :path: ../ + +SPEC CHECKSUMS: + JSQMessagesViewController: c11b9e77372ab72c45c67311f6da7342e32df1e8 + +PODFILE CHECKSUM: b51479be92e08cc1504a6937b0ded5693c7d1bd9 + +COCOAPODS: 1.0.1 diff --git a/SwiftExample/Pods/Pods.xcodeproj/project.pbxproj b/SwiftExample/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 000000000..ac17e928d --- /dev/null +++ b/SwiftExample/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,1170 @@ +// !$*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 */; +} diff --git a/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/Info.plist b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/Info.plist new file mode 100644 index 000000000..5e150f81e --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/Info.plist @@ -0,0 +1,26 @@ + + + + + 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 + + + diff --git a/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-dummy.m b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-dummy.m new file mode 100644 index 000000000..363ff3454 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_JSQMessagesViewController : NSObject +@end +@implementation PodsDummy_JSQMessagesViewController +@end diff --git a/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-prefix.pch b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-prefix.pch new file mode 100644 index 000000000..aa992a4ad --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-prefix.pch @@ -0,0 +1,4 @@ +#ifdef __OBJC__ +#import +#endif + diff --git a/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-umbrella.h b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-umbrella.h new file mode 100644 index 000000000..5739e9903 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController-umbrella.h @@ -0,0 +1,52 @@ +#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[]; + diff --git a/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController.modulemap b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController.modulemap new file mode 100644 index 000000000..4b4a90fc6 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController.modulemap @@ -0,0 +1,6 @@ +framework module JSQMessagesViewController { + umbrella header "JSQMessagesViewController-umbrella.h" + + export * + module * { export * } +} diff --git a/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController.xcconfig b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController.xcconfig new file mode 100644 index 000000000..e92c26849 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/JSQMessagesViewController/JSQMessagesViewController.xcconfig @@ -0,0 +1,9 @@ +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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Info.plist b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Info.plist new file mode 100644 index 000000000..2243fe6e2 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Info.plist @@ -0,0 +1,26 @@ + + + + + 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 + + + diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-acknowledgements.markdown b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-acknowledgements.markdown new file mode 100644 index 000000000..4a727e434 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-acknowledgements.markdown @@ -0,0 +1,27 @@ +# 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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-acknowledgements.plist b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-acknowledgements.plist new file mode 100644 index 000000000..ec0e739a3 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-acknowledgements.plist @@ -0,0 +1,57 @@ + + + + + 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 + + diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-dummy.m b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-dummy.m new file mode 100644 index 000000000..e70a280f8 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_SwiftExample : NSObject +@end +@implementation PodsDummy_Pods_SwiftExample +@end diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-frameworks.sh b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-frameworks.sh new file mode 100755 index 000000000..11f0e4635 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-frameworks.sh @@ -0,0 +1,91 @@ +#!/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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-resources.sh b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-resources.sh new file mode 100755 index 000000000..0a1561528 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-resources.sh @@ -0,0 +1,102 @@ +#!/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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-umbrella.h b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-umbrella.h new file mode 100644 index 000000000..21616e694 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample-umbrella.h @@ -0,0 +1,6 @@ +#import + + +FOUNDATION_EXPORT double Pods_SwiftExampleVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_SwiftExampleVersionString[]; + diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.debug.xcconfig b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.debug.xcconfig new file mode 100644 index 000000000..c23e0f65d --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.debug.xcconfig @@ -0,0 +1,8 @@ +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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.modulemap b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.modulemap new file mode 100644 index 000000000..bc29e6a71 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.modulemap @@ -0,0 +1,6 @@ +framework module Pods_SwiftExample { + umbrella header "Pods-SwiftExample-umbrella.h" + + export * + module * { export * } +} diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.release.xcconfig b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.release.xcconfig new file mode 100644 index 000000000..c23e0f65d --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExample/Pods-SwiftExample.release.xcconfig @@ -0,0 +1,8 @@ +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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Info.plist b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Info.plist new file mode 100644 index 000000000..2243fe6e2 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Info.plist @@ -0,0 +1,26 @@ + + + + + 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 + + + diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-acknowledgements.markdown b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-acknowledgements.markdown new file mode 100644 index 000000000..4a727e434 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-acknowledgements.markdown @@ -0,0 +1,27 @@ +# 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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-acknowledgements.plist b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-acknowledgements.plist new file mode 100644 index 000000000..ec0e739a3 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-acknowledgements.plist @@ -0,0 +1,57 @@ + + + + + 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 + + diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-dummy.m b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-dummy.m new file mode 100644 index 000000000..6a6fd9e3c --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_SwiftExampleTests : NSObject +@end +@implementation PodsDummy_Pods_SwiftExampleTests +@end diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-frameworks.sh b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-frameworks.sh new file mode 100755 index 000000000..11f0e4635 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-frameworks.sh @@ -0,0 +1,91 @@ +#!/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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-resources.sh b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-resources.sh new file mode 100755 index 000000000..0a1561528 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-resources.sh @@ -0,0 +1,102 @@ +#!/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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-umbrella.h b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-umbrella.h new file mode 100644 index 000000000..5b5bf08e3 --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests-umbrella.h @@ -0,0 +1,6 @@ +#import + + +FOUNDATION_EXPORT double Pods_SwiftExampleTestsVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_SwiftExampleTestsVersionString[]; + diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.debug.xcconfig b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.debug.xcconfig new file mode 100644 index 000000000..c23e0f65d --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.debug.xcconfig @@ -0,0 +1,8 @@ +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 diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.modulemap b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.modulemap new file mode 100644 index 000000000..9c77611fd --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.modulemap @@ -0,0 +1,6 @@ +framework module Pods_SwiftExampleTests { + umbrella header "Pods-SwiftExampleTests-umbrella.h" + + export * + module * { export * } +} diff --git a/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.release.xcconfig b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.release.xcconfig new file mode 100644 index 000000000..c23e0f65d --- /dev/null +++ b/SwiftExample/Pods/Target Support Files/Pods-SwiftExampleTests/Pods-SwiftExampleTests.release.xcconfig @@ -0,0 +1,8 @@ +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 diff --git a/SwiftExample/SwiftExample.xcodeproj/project.pbxproj b/SwiftExample/SwiftExample.xcodeproj/project.pbxproj new file mode 100644 index 000000000..0d40caf50 --- /dev/null +++ b/SwiftExample/SwiftExample.xcodeproj/project.pbxproj @@ -0,0 +1,577 @@ +// !$*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 */; +} diff --git a/SwiftExample/SwiftExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SwiftExample/SwiftExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..feaeda14a --- /dev/null +++ b/SwiftExample/SwiftExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SwiftExample/SwiftExample.xcworkspace/contents.xcworkspacedata b/SwiftExample/SwiftExample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..238f90ac3 --- /dev/null +++ b/SwiftExample/SwiftExample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/SwiftExample/SwiftExample/AppDelegate.swift b/SwiftExample/SwiftExample/AppDelegate.swift new file mode 100644 index 000000000..b5f280a9e --- /dev/null +++ b/SwiftExample/SwiftExample/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// 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:. + } + + +} + diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-167.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-167.png new file mode 100644 index 000000000..62482f415 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-167.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29.png new file mode 100644 index 000000000..2ff6e7aa0 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png new file mode 100644 index 000000000..ab8d5ba9f Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png new file mode 100644 index 000000000..8bd09d719 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png new file mode 100644 index 000000000..c17579d54 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40.png new file mode 100644 index 000000000..fa39765c5 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 000000000..8ced5f0a2 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png new file mode 100644 index 000000000..c425c198a Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 000000000..c567b0387 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 000000000..1799534d6 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 000000000..5606a3d3b Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png new file mode 100644 index 000000000..728210b90 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/icon40@3x.png b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/icon40@3x.png new file mode 100644 index 000000000..04e689a27 Binary files /dev/null and b/SwiftExample/SwiftExample/Assets.xcassets/AppIcon.appiconset/icon40@3x.png differ diff --git a/SwiftExample/SwiftExample/Base.lproj/LaunchScreen.storyboard b/SwiftExample/SwiftExample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..2e721e183 --- /dev/null +++ b/SwiftExample/SwiftExample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftExample/SwiftExample/Base.lproj/Main.storyboard b/SwiftExample/SwiftExample/Base.lproj/Main.storyboard new file mode 100644 index 000000000..84c40cfc0 --- /dev/null +++ b/SwiftExample/SwiftExample/Base.lproj/Main.storyboard @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftExample/SwiftExample/ChatViewController.swift b/SwiftExample/SwiftExample/ChatViewController.swift new file mode 100644 index 000000000..fbf824f68 --- /dev/null +++ b/SwiftExample/SwiftExample/ChatViewController.swift @@ -0,0 +1,437 @@ +// +// 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; + } + +} diff --git a/SwiftExample/SwiftExample/Conversation.swift b/SwiftExample/SwiftExample/Conversation.swift new file mode 100644 index 000000000..a530affdc --- /dev/null +++ b/SwiftExample/SwiftExample/Conversation.swift @@ -0,0 +1,19 @@ +// +// 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 +} diff --git a/SwiftExample/SwiftExample/DemoConversation.swift b/SwiftExample/SwiftExample/DemoConversation.swift new file mode 100644 index 000000000..7dbde4456 --- /dev/null +++ b/SwiftExample/SwiftExample/DemoConversation.swift @@ -0,0 +1,123 @@ +// +// 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 +} diff --git a/SwiftExample/SwiftExample/Info.plist b/SwiftExample/SwiftExample/Info.plist new file mode 100644 index 000000000..40c6215d9 --- /dev/null +++ b/SwiftExample/SwiftExample/Info.plist @@ -0,0 +1,47 @@ + + + + + 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 + + + diff --git a/SwiftExample/SwiftExample/InitalTableViewController.swift b/SwiftExample/SwiftExample/InitalTableViewController.swift new file mode 100644 index 000000000..b0fa2a62f --- /dev/null +++ b/SwiftExample/SwiftExample/InitalTableViewController.swift @@ -0,0 +1,130 @@ +// +// 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 + } + } + + + +} diff --git a/SwiftExample/SwiftExample/SettingsTableViewController.swift b/SwiftExample/SwiftExample/SettingsTableViewController.swift new file mode 100644 index 000000000..b85aed21e --- /dev/null +++ b/SwiftExample/SwiftExample/SettingsTableViewController.swift @@ -0,0 +1,73 @@ +// +// 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 + } +} diff --git a/SwiftExample/SwiftExampleTests/ChatViewControllerTests.swift b/SwiftExample/SwiftExampleTests/ChatViewControllerTests.swift new file mode 100644 index 000000000..c89115b5a --- /dev/null +++ b/SwiftExample/SwiftExampleTests/ChatViewControllerTests.swift @@ -0,0 +1,227 @@ +// +// 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") + + } + +} diff --git a/SwiftExample/SwiftExampleTests/Info.plist b/SwiftExample/SwiftExampleTests/Info.plist new file mode 100644 index 000000000..ba72822e8 --- /dev/null +++ b/SwiftExample/SwiftExampleTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/SwiftExample/SwiftExampleTests/SettingsTests.swift b/SwiftExample/SwiftExampleTests/SettingsTests.swift new file mode 100644 index 000000000..2a2c61600 --- /dev/null +++ b/SwiftExample/SwiftExampleTests/SettingsTests.swift @@ -0,0 +1,24 @@ +// +// 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) + } +}