diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..aea3bc33e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,12 @@ +## New issue checklist + +- [ ] I have reviewed the [`README`](https://github.com/Instagram/IGListKit/blob/master/README.md) and [documentation](http://instagram.github.io/IGListKit) +- [ ] I have searched [existing issues](https://github.com/Instagram/IGListKit/issues) and this is not a duplicate + +### General information + +- Library version(s): +- iOS version(s): +- Devices/Simulators affected: +- Reproducible in the demo project? (Yes/No): +- Related issues: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..b8d18b4d3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +## Changes in this pull request + + + +## Pull request checklist + +- [ ] All tests pass. Demo project builds and runs. +- [ ] I added tests, an experiment, or detailed why my change isn't tested. +- [ ] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/CONTRIBUTING.md) diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ddbcb7dd8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +.DS_Store + +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xcuserstate + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Docs +docs/docsets/ + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +.build/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..0c4d55b83 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,44 @@ +language: objective-c +osx_image: xcode8 + +env: + global: + - LANG=en_US.UTF-8 + + - PROJECT="IGListKit.xcodeproj" + - IOS_SCHEME="IGListKit" + - IOS_SDK=iphonesimulator10.0 + + matrix: + - DESTINATION="OS=8.1,name=iPhone 4s" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="YES" + - DESTINATION="OS=8.2,name=iPhone 5" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=8.3,name=iPhone 5s" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=8.4,name=iPhone 6" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + + - DESTINATION="OS=9.0,name=iPhone 6 Plus" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + - DESTINATION="OS=9.1,name=iPhone 6s" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=9.2,name=iPhone 6s Plus" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=9.3,name=iPhone 7" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=10.0,name=iPhone 7 Plus" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + + - DESTINATION="OS=8.1,name=iPad 2" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + - DESTINATION="OS=9.0,name=iPad Air" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=10.0,name=iPad Pro" SDK="$IOS_SDK" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + +script: +- if [ $POD_LINT == "YES" ]; then + pod spec lint; + fi + + +- if [ $BUILD_EXAMPLE == "YES" ]; then + xcodebuild clean build -project Example/IGListKitExamples.xcworkspace -scheme IGListKitExamples -sdk "$SDK" -destination "$DESTINATION" ONLY_ACTIVE_ARCH=NO | xcpretty -c; + fi + + +- if [ $RUN_TESTS == "YES" ]; then + xcodebuild test -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO | xcpretty -c; + else + xcodebuild clean build -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO | xcpretty -c; + fi + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..c606e39e3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# CHANGELOG + +The changelog for `IGListKit`. Also see the [releases](https://github.com/instagram/IGListKit/releases) on GitHub. + +1.0.0 +----- + +Initial release. :tada: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 000000000..616c370ec --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing to IGListKit + +We want to make contributing to this project as easy and transparent as +possible, and actively welcome your pull requests. If you run into problems, +please open an issue on GitHub. + +## Pull Requests + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Experimental changes + +If your change can't be unit tested, we might ask that you add your change as an experiment so that we can verify your change works. To do this, first add a new option to [IGListExperiment](https://github.com/Instagram/IGListKit/blob/master/Source/IGListExperiments.h#L17). + +Then, use an `experiments` bitmask wherever your change is and wrap it in a check to see if it is enabled: + +```swift +IGListExperimentEnabled(self.experiments, IGListExperimentMyAwesomeChange) { + // your code here +} +``` + +Once your experiment is confirmed we will remove the option and wrapping check! + +## Contributor License Agreement ("CLA") + +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues + +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## Coding Style + +* 4 spaces for indentation rather than tabs +* Public classes and methods must contain header documentation +* When changing header docs, make sure to run the [jazzy](https://github.com/realm/jazzy) doc script: `./build_docs.sh` +* Use C functions whenever possible + +## Updating Testing Dependencies + +If you need a different version of one of the testing dependencies, you will need to first [install Cocoapods](https://guides.cocoapods.org/using/getting-started.html): + +``` +$ [sudo] gem install cocoapods +``` + +Then within the project directory, run: + +``` +$ pod install +``` + +to update the dependency to that version. + +## License + +By contributing to `IGListKit`, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree. diff --git a/Example/IGListKitExamples.xcodeproj/project.pbxproj b/Example/IGListKitExamples.xcodeproj/project.pbxproj new file mode 100644 index 000000000..f2e9855bf --- /dev/null +++ b/Example/IGListKitExamples.xcodeproj/project.pbxproj @@ -0,0 +1,492 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 2942FF8C1D9F39E00015D24B /* DemoSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF831D9F39E00015D24B /* DemoSectionController.swift */; }; + 2942FF8D1D9F39E00015D24B /* EmbeddedSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF841D9F39E00015D24B /* EmbeddedSectionController.swift */; }; + 2942FF8E1D9F39E00015D24B /* ExpandableSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF851D9F39E00015D24B /* ExpandableSectionController.swift */; }; + 2942FF8F1D9F39E00015D24B /* GridSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF861D9F39E00015D24B /* GridSectionController.swift */; }; + 2942FF901D9F39E00015D24B /* HorizontalSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF871D9F39E00015D24B /* HorizontalSectionController.swift */; }; + 2942FF911D9F39E00015D24B /* LabelSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF881D9F39E00015D24B /* LabelSectionController.swift */; }; + 2942FF921D9F39E00015D24B /* RemoveSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF891D9F39E00015D24B /* RemoveSectionController.swift */; }; + 2942FF931D9F39E00015D24B /* SearchSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF8A1D9F39E00015D24B /* SearchSectionController.swift */; }; + 2942FF941D9F39E00015D24B /* UserSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF8B1D9F39E00015D24B /* UserSectionController.swift */; }; + 2961B38E1D68B031001C9451 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2961B38D1D68B031001C9451 /* AppDelegate.swift */; }; + 2961B3951D68B031001C9451 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2961B3941D68B031001C9451 /* Assets.xcassets */; }; + 2961B3981D68B031001C9451 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2961B3961D68B031001C9451 /* LaunchScreen.storyboard */; }; + 2961B3AB1D68B0B5001C9451 /* DemosViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2961B3A41D68B0B5001C9451 /* DemosViewController.swift */; }; + 2961B3AC1D68B0B5001C9451 /* LoadMoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2961B3A51D68B0B5001C9451 /* LoadMoreViewController.swift */; }; + 2961B3AD1D68B0B5001C9451 /* LabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2961B3A71D68B0B5001C9451 /* LabelCell.swift */; }; + 2961B3AE1D68B0B5001C9451 /* SpinnerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2961B3A81D68B0B5001C9451 /* SpinnerCell.swift */; }; + 2961B3B01D68B28E001C9451 /* SearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2961B3AF1D68B28E001C9451 /* SearchCell.swift */; }; + 29628F141D91905A0026B15A /* DetailLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29628F131D91905A0026B15A /* DetailLabelCell.swift */; }; + 299068281D75BFEC00A62888 /* MixedDataViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299068271D75BFEC00A62888 /* MixedDataViewController.swift */; }; + 2991F9191D7BADC900B0C58F /* CenterLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991F9181D7BADC900B0C58F /* CenterLabelCell.swift */; }; + 2991F91E1D7BB30C00B0C58F /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991F91D1D7BB30C00B0C58F /* User.swift */; }; + 2991F9241D7BB89F00B0C58F /* NestedAdapterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991F9231D7BB89F00B0C58F /* NestedAdapterViewController.swift */; }; + 2991F9281D7BB9EC00B0C58F /* EmbeddedCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991F9271D7BB9EC00B0C58F /* EmbeddedCollectionViewCell.swift */; }; + 2991F92C1D7BBE5400B0C58F /* RemoveCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991F92B1D7BBE5400B0C58F /* RemoveCell.swift */; }; + 2991F9301D7BC0E400B0C58F /* EmptyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991F92F1D7BC0E400B0C58F /* EmptyViewController.swift */; }; + 299B54001D6BD6630074A202 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299B53FF1D6BD6630074A202 /* SearchViewController.swift */; }; + 814F1E00410200822610BB49 /* Pods_IGListKitExamples.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52A8DC2D07A93D7AA55BC993 /* Pods_IGListKitExamples.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2942FF831D9F39E00015D24B /* DemoSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoSectionController.swift; sourceTree = ""; }; + 2942FF841D9F39E00015D24B /* EmbeddedSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedSectionController.swift; sourceTree = ""; }; + 2942FF851D9F39E00015D24B /* ExpandableSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpandableSectionController.swift; sourceTree = ""; }; + 2942FF861D9F39E00015D24B /* GridSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridSectionController.swift; sourceTree = ""; }; + 2942FF871D9F39E00015D24B /* HorizontalSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalSectionController.swift; sourceTree = ""; }; + 2942FF881D9F39E00015D24B /* LabelSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelSectionController.swift; sourceTree = ""; }; + 2942FF891D9F39E00015D24B /* RemoveSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoveSectionController.swift; sourceTree = ""; }; + 2942FF8A1D9F39E00015D24B /* SearchSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchSectionController.swift; sourceTree = ""; }; + 2942FF8B1D9F39E00015D24B /* UserSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserSectionController.swift; sourceTree = ""; }; + 2961B38A1D68B031001C9451 /* IGListKitExamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IGListKitExamples.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2961B38D1D68B031001C9451 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 2961B3941D68B031001C9451 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2961B3971D68B031001C9451 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 2961B3991D68B031001C9451 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2961B3A41D68B0B5001C9451 /* DemosViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemosViewController.swift; sourceTree = ""; }; + 2961B3A51D68B0B5001C9451 /* LoadMoreViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadMoreViewController.swift; sourceTree = ""; }; + 2961B3A71D68B0B5001C9451 /* LabelCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelCell.swift; sourceTree = ""; }; + 2961B3A81D68B0B5001C9451 /* SpinnerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpinnerCell.swift; sourceTree = ""; }; + 2961B3AF1D68B28E001C9451 /* SearchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchCell.swift; sourceTree = ""; }; + 29628F131D91905A0026B15A /* DetailLabelCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailLabelCell.swift; sourceTree = ""; }; + 299068271D75BFEC00A62888 /* MixedDataViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixedDataViewController.swift; sourceTree = ""; }; + 2991F9181D7BADC900B0C58F /* CenterLabelCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CenterLabelCell.swift; sourceTree = ""; }; + 2991F91D1D7BB30C00B0C58F /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 2991F9231D7BB89F00B0C58F /* NestedAdapterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NestedAdapterViewController.swift; sourceTree = ""; }; + 2991F9271D7BB9EC00B0C58F /* EmbeddedCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedCollectionViewCell.swift; sourceTree = ""; }; + 2991F92B1D7BBE5400B0C58F /* RemoveCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoveCell.swift; sourceTree = ""; }; + 2991F92F1D7BC0E400B0C58F /* EmptyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyViewController.swift; sourceTree = ""; }; + 299B53FF1D6BD6630074A202 /* SearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; + 4125DCD99578FDEF3C373BA0 /* Pods-IGListKitExamples.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitExamples.release.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig"; sourceTree = ""; }; + 52A8DC2D07A93D7AA55BC993 /* Pods_IGListKitExamples.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IGListKitExamples.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FE05AB853448A0705AF80427 /* Pods-IGListKitExamples.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitExamples.debug.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2961B3871D68B031001C9451 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 814F1E00410200822610BB49 /* Pods_IGListKitExamples.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0853E82EF5C5E4BD4E0760E3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 52A8DC2D07A93D7AA55BC993 /* Pods_IGListKitExamples.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 2942FF821D9F39E00015D24B /* SectionControllers */ = { + isa = PBXGroup; + children = ( + 2942FF831D9F39E00015D24B /* DemoSectionController.swift */, + 2942FF841D9F39E00015D24B /* EmbeddedSectionController.swift */, + 2942FF851D9F39E00015D24B /* ExpandableSectionController.swift */, + 2942FF861D9F39E00015D24B /* GridSectionController.swift */, + 2942FF871D9F39E00015D24B /* HorizontalSectionController.swift */, + 2942FF881D9F39E00015D24B /* LabelSectionController.swift */, + 2942FF891D9F39E00015D24B /* RemoveSectionController.swift */, + 2942FF8A1D9F39E00015D24B /* SearchSectionController.swift */, + 2942FF8B1D9F39E00015D24B /* UserSectionController.swift */, + ); + path = SectionControllers; + sourceTree = ""; + }; + 2961B3811D68B031001C9451 = { + isa = PBXGroup; + children = ( + 2961B38C1D68B031001C9451 /* IGListKitExamples */, + 2961B38B1D68B031001C9451 /* Products */, + 586CEEF7A60B53C23ED20E47 /* Pods */, + 0853E82EF5C5E4BD4E0760E3 /* Frameworks */, + ); + sourceTree = ""; + }; + 2961B38B1D68B031001C9451 /* Products */ = { + isa = PBXGroup; + children = ( + 2961B38A1D68B031001C9451 /* IGListKitExamples.app */, + ); + name = Products; + sourceTree = ""; + }; + 2961B38C1D68B031001C9451 /* IGListKitExamples */ = { + isa = PBXGroup; + children = ( + 2961B38D1D68B031001C9451 /* AppDelegate.swift */, + 2961B3941D68B031001C9451 /* Assets.xcassets */, + 2961B3991D68B031001C9451 /* Info.plist */, + 2961B3961D68B031001C9451 /* LaunchScreen.storyboard */, + 2991F91C1D7BB30300B0C58F /* Models */, + 2942FF821D9F39E00015D24B /* SectionControllers */, + 2961B3A31D68B0B5001C9451 /* ViewControllers */, + 2961B3A61D68B0B5001C9451 /* Views */, + ); + path = IGListKitExamples; + sourceTree = ""; + }; + 2961B3A31D68B0B5001C9451 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 2961B3A41D68B0B5001C9451 /* DemosViewController.swift */, + 2991F92F1D7BC0E400B0C58F /* EmptyViewController.swift */, + 2961B3A51D68B0B5001C9451 /* LoadMoreViewController.swift */, + 299068271D75BFEC00A62888 /* MixedDataViewController.swift */, + 2991F9231D7BB89F00B0C58F /* NestedAdapterViewController.swift */, + 299B53FF1D6BD6630074A202 /* SearchViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + 2961B3A61D68B0B5001C9451 /* Views */ = { + isa = PBXGroup; + children = ( + 2991F9181D7BADC900B0C58F /* CenterLabelCell.swift */, + 29628F131D91905A0026B15A /* DetailLabelCell.swift */, + 2991F9271D7BB9EC00B0C58F /* EmbeddedCollectionViewCell.swift */, + 2961B3A71D68B0B5001C9451 /* LabelCell.swift */, + 2991F92B1D7BBE5400B0C58F /* RemoveCell.swift */, + 2961B3AF1D68B28E001C9451 /* SearchCell.swift */, + 2961B3A81D68B0B5001C9451 /* SpinnerCell.swift */, + ); + path = Views; + sourceTree = ""; + }; + 2991F91C1D7BB30300B0C58F /* Models */ = { + isa = PBXGroup; + children = ( + 2991F91D1D7BB30C00B0C58F /* User.swift */, + ); + path = Models; + sourceTree = ""; + }; + 586CEEF7A60B53C23ED20E47 /* Pods */ = { + isa = PBXGroup; + children = ( + FE05AB853448A0705AF80427 /* Pods-IGListKitExamples.debug.xcconfig */, + 4125DCD99578FDEF3C373BA0 /* Pods-IGListKitExamples.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2961B3891D68B031001C9451 /* IGListKitExamples */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2961B39C1D68B031001C9451 /* Build configuration list for PBXNativeTarget "IGListKitExamples" */; + buildPhases = ( + 6951D533B2BCD93C5834534A /* [CP] Check Pods Manifest.lock */, + 2961B3861D68B031001C9451 /* Sources */, + 2961B3871D68B031001C9451 /* Frameworks */, + 2961B3881D68B031001C9451 /* Resources */, + 039A6995573B39194F228EF3 /* [CP] Embed Pods Frameworks */, + 206BF58DBD30DB5B770B9684 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IGListKitExamples; + productName = IGListKitExamples; + productReference = 2961B38A1D68B031001C9451 /* IGListKitExamples.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2961B3821D68B031001C9451 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0730; + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = Instagram; + TargetAttributes = { + 2961B3891D68B031001C9451 = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 2961B3851D68B031001C9451 /* Build configuration list for PBXProject "IGListKitExamples" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 2961B3811D68B031001C9451; + productRefGroup = 2961B38B1D68B031001C9451 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2961B3891D68B031001C9451 /* IGListKitExamples */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2961B3881D68B031001C9451 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2961B3981D68B031001C9451 /* LaunchScreen.storyboard in Resources */, + 2961B3951D68B031001C9451 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 039A6995573B39194F228EF3 /* [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-IGListKitExamples/Pods-IGListKitExamples-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 206BF58DBD30DB5B770B9684 /* [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-IGListKitExamples/Pods-IGListKitExamples-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 6951D533B2BCD93C5834534A /* [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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2961B3861D68B031001C9451 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 299068281D75BFEC00A62888 /* MixedDataViewController.swift in Sources */, + 299B54001D6BD6630074A202 /* SearchViewController.swift in Sources */, + 2961B3AE1D68B0B5001C9451 /* SpinnerCell.swift in Sources */, + 2961B3B01D68B28E001C9451 /* SearchCell.swift in Sources */, + 2942FF8C1D9F39E00015D24B /* DemoSectionController.swift in Sources */, + 2942FF931D9F39E00015D24B /* SearchSectionController.swift in Sources */, + 2942FF911D9F39E00015D24B /* LabelSectionController.swift in Sources */, + 2961B3AC1D68B0B5001C9451 /* LoadMoreViewController.swift in Sources */, + 2991F9191D7BADC900B0C58F /* CenterLabelCell.swift in Sources */, + 29628F141D91905A0026B15A /* DetailLabelCell.swift in Sources */, + 2991F9301D7BC0E400B0C58F /* EmptyViewController.swift in Sources */, + 2991F91E1D7BB30C00B0C58F /* User.swift in Sources */, + 2991F9241D7BB89F00B0C58F /* NestedAdapterViewController.swift in Sources */, + 2961B38E1D68B031001C9451 /* AppDelegate.swift in Sources */, + 2942FF941D9F39E00015D24B /* UserSectionController.swift in Sources */, + 2991F92C1D7BBE5400B0C58F /* RemoveCell.swift in Sources */, + 2942FF8D1D9F39E00015D24B /* EmbeddedSectionController.swift in Sources */, + 2991F9281D7BB9EC00B0C58F /* EmbeddedCollectionViewCell.swift in Sources */, + 2942FF8F1D9F39E00015D24B /* GridSectionController.swift in Sources */, + 2942FF921D9F39E00015D24B /* RemoveSectionController.swift in Sources */, + 2961B3AD1D68B0B5001C9451 /* LabelCell.swift in Sources */, + 2942FF901D9F39E00015D24B /* HorizontalSectionController.swift in Sources */, + 2961B3AB1D68B0B5001C9451 /* DemosViewController.swift in Sources */, + 2942FF8E1D9F39E00015D24B /* ExpandableSectionController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 2961B3961D68B031001C9451 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 2961B3971D68B031001C9451 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 2961B39A1D68B031001C9451 /* 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; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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"; + }; + name = Debug; + }; + 2961B39B1D68B031001C9451 /* 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; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 2961B39D1D68B031001C9451 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FE05AB853448A0705AF80427 /* Pods-IGListKitExamples.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = IGListKitExamples/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.instagram.IGListKitExamples; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 2961B39E1D68B031001C9451 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4125DCD99578FDEF3C373BA0 /* Pods-IGListKitExamples.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = IGListKitExamples/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.instagram.IGListKitExamples; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2961B3851D68B031001C9451 /* Build configuration list for PBXProject "IGListKitExamples" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2961B39A1D68B031001C9451 /* Debug */, + 2961B39B1D68B031001C9451 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2961B39C1D68B031001C9451 /* Build configuration list for PBXNativeTarget "IGListKitExamples" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2961B39D1D68B031001C9451 /* Debug */, + 2961B39E1D68B031001C9451 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 2961B3821D68B031001C9451 /* Project object */; +} diff --git a/Example/IGListKitExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/IGListKitExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..5cc62890c --- /dev/null +++ b/Example/IGListKitExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/IGListKitExamples.xcworkspace/contents.xcworkspacedata b/Example/IGListKitExamples.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..106ba5d01 --- /dev/null +++ b/Example/IGListKitExamples.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example/IGListKitExamples/AppDelegate.swift b/Example/IGListKitExamples/AppDelegate.swift new file mode 100644 index 000000000..c6e2a09e9 --- /dev/null +++ b/Example/IGListKitExamples/AppDelegate.swift @@ -0,0 +1,30 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = UINavigationController(rootViewController: DemosViewController()) + window?.makeKeyAndVisible() + return true + } + +} + diff --git a/Example/IGListKitExamples/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/IGListKitExamples/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..118c98f74 --- /dev/null +++ b/Example/IGListKitExamples/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/IGListKitExamples/Base.lproj/LaunchScreen.storyboard b/Example/IGListKitExamples/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..49be0c96a --- /dev/null +++ b/Example/IGListKitExamples/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/IGListKitExamples/Info.plist b/Example/IGListKitExamples/Info.plist new file mode 100644 index 000000000..9675a6e08 --- /dev/null +++ b/Example/IGListKitExamples/Info.plist @@ -0,0 +1,41 @@ + + + + + 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 + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/Example/IGListKitExamples/Models/User.swift b/Example/IGListKitExamples/Models/User.swift new file mode 100644 index 000000000..39a3082cc --- /dev/null +++ b/Example/IGListKitExamples/Models/User.swift @@ -0,0 +1,45 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import IGListKit + +class User: IGListDiffable { + + let pk: Int + let name: String + let handle: String + + init(pk: Int, name: String, handle: String) { + self.pk = pk + self.name = name + self.handle = handle + } + + //MARK: IGListDiffable + + func diffIdentifier() -> NSObjectProtocol { + return pk as NSObjectProtocol + } + + func isEqual(_ object: IGListDiffable?) -> Bool { + if self === object { + return true + } + if let object = object as? User { + return name == object.name && handle == object.handle + } + return false + } + +} diff --git a/Example/IGListKitExamples/SectionControllers/DemoSectionController.swift b/Example/IGListKitExamples/SectionControllers/DemoSectionController.swift new file mode 100644 index 000000000..99d8805db --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/DemoSectionController.swift @@ -0,0 +1,62 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class DemoItem: NSObject { + + let name: String + let controllerClass: UIViewController.Type + + init( + name: String, + controllerClass: UIViewController.Type + ) { + self.name = name + self.controllerClass = controllerClass + } + +} + +class DemoSectionController: IGListSectionController, IGListSectionType { + + var object: DemoItem? + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + return CGSize(width: collectionContext!.containerSize.width, height: 55) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCell(of: LabelCell.self, for: self, at: index) as! LabelCell + cell.label.text = object?.name + return cell + } + + func didUpdate(to object: Any) { + self.object = object as? DemoItem + } + + func didSelectItem(at index: Int) { + if let controller = object?.controllerClass.init() { + controller.title = object?.name + viewController?.navigationController?.pushViewController(controller, animated: true) + } + } + +} diff --git a/Example/IGListKitExamples/SectionControllers/EmbeddedSectionController.swift b/Example/IGListKitExamples/SectionControllers/EmbeddedSectionController.swift new file mode 100644 index 000000000..01f1bfcfd --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/EmbeddedSectionController.swift @@ -0,0 +1,50 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class EmbeddedSectionController: IGListSectionController, IGListSectionType { + + var number: Int? + + override init() { + super.init() + self.inset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10) + } + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + let height = collectionContext?.containerSize.height ?? 0 + return CGSize(width: height, height: height) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCell(of: CenterLabelCell.self, for: self, at: index) as! CenterLabelCell + let value = number ?? 0 + cell.label.text = "\(value + 1)" + cell.backgroundColor = UIColor(red: 237/255.0, green: 73/255.0, blue: 86/255.0, alpha: 1) + return cell + } + + func didUpdate(to object: Any) { + number = object as? Int + } + + func didSelectItem(at index: Int) {} + +} diff --git a/Example/IGListKitExamples/SectionControllers/ExpandableSectionController.swift b/Example/IGListKitExamples/SectionControllers/ExpandableSectionController.swift new file mode 100644 index 000000000..d4ed4520e --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/ExpandableSectionController.swift @@ -0,0 +1,49 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class ExpandableSectionController: IGListSectionController, IGListSectionType { + + var expanded = false + var object: String? + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + let width = collectionContext!.containerSize.width + let height = expanded ? LabelCell.textHeight(object ?? "", width: width) : LabelCell.singleLineHeight + return CGSize(width: width, height: height) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCell(of: LabelCell.self, for: self, at: index) as! LabelCell + cell.label.numberOfLines = expanded ? 0 : 1 + cell.label.text = object + return cell + } + + func didUpdate(to object: Any) { + self.object = object as? String + } + + func didSelectItem(at index: Int) { + expanded = !expanded + collectionContext?.reload(self) + } + +} diff --git a/Example/IGListKitExamples/SectionControllers/GridSectionController.swift b/Example/IGListKitExamples/SectionControllers/GridSectionController.swift new file mode 100644 index 000000000..511e5f2f9 --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/GridSectionController.swift @@ -0,0 +1,63 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class GridItem: NSObject { + + let color: UIColor + let itemCount: Int + + init(color: UIColor, itemCount: Int) { + self.color = color + self.itemCount = itemCount + } + +} + +class GridSectionController: IGListSectionController, IGListSectionType { + + var object: GridItem? + + override init() { + super.init() + self.minimumInteritemSpacing = 1 + self.minimumLineSpacing = 1 + } + + func numberOfItems() -> Int { + return object?.itemCount ?? 0 + } + + func sizeForItem(at index: Int) -> CGSize { + let width = collectionContext?.containerSize.width ?? 0 + let itemSize = floor(width / 4) + return CGSize(width: itemSize, height: itemSize) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCell(of: CenterLabelCell.self, for: self, at: index) as! CenterLabelCell + cell.label.text = "\(index + 1)" + cell.backgroundColor = object?.color + return cell + } + + func didUpdate(to object: Any) { + self.object = object as? GridItem + } + + func didSelectItem(at index: Int) {} + +} diff --git a/Example/IGListKitExamples/SectionControllers/HorizontalSectionController.swift b/Example/IGListKitExamples/SectionControllers/HorizontalSectionController.swift new file mode 100644 index 000000000..f6b5d3615 --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/HorizontalSectionController.swift @@ -0,0 +1,68 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class HorizontalSectionController: IGListSectionController, IGListSectionType, IGListAdapterDataSource { + + var number: Int? + + lazy var adapter: IGListAdapter = { + let adapter = IGListAdapter(updater: IGListAdapterUpdater(), + viewController: self.viewController, + workingRangeSize: 0) + adapter.dataSource = self + return adapter + }() + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + return CGSize(width: collectionContext!.containerSize.width, height: 100) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCell(of: EmbeddedCollectionViewCell.self, for: self, at: index) as! EmbeddedCollectionViewCell + adapter.collectionView = cell.collectionView + return cell + } + + func didUpdate(to object: Any) { + number = object as? Int + } + + func didSelectItem(at index: Int) {} + + //MARK: IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + var numbers = [Int]() + for i in 0..<(number ?? 0) { + numbers.append(i) + } + return numbers as [IGListDiffable] + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + return EmbeddedSectionController() + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { + return nil + } + +} diff --git a/Example/IGListKitExamples/SectionControllers/LabelSectionController.swift b/Example/IGListKitExamples/SectionControllers/LabelSectionController.swift new file mode 100644 index 000000000..a848f3ee3 --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/LabelSectionController.swift @@ -0,0 +1,42 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class LabelSectionController: IGListSectionController, IGListSectionType { + + var object: String? + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + return CGSize(width: collectionContext!.containerSize.width, height: 55) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCell(of: LabelCell.self, for: self, at: index) as! LabelCell + cell.label.text = object + return cell + } + + func didUpdate(to object: Any) { + self.object = object as? String + } + + func didSelectItem(at index: Int) {} + +} diff --git a/Example/IGListKitExamples/SectionControllers/RemoveSectionController.swift b/Example/IGListKitExamples/SectionControllers/RemoveSectionController.swift new file mode 100644 index 000000000..8fd6273b2 --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/RemoveSectionController.swift @@ -0,0 +1,58 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import IGListKit + +protocol RemoveSectionControllerDelegate: class { + func removeSectionControllerWantsRemoved(_ sectionController: RemoveSectionController) +} + +class RemoveSectionController: IGListSectionController, IGListSectionType, RemoveCellDelegate { + + weak var delegate: RemoveSectionControllerDelegate? + var number: Int? + + override init() { + super.init() + inset = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0) + } + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + return CGSize(width: collectionContext!.containerSize.width, height: 55) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext?.dequeueReusableCell(of: RemoveCell.self, for: self, at: index) as! RemoveCell + cell.label.text = "Cell: \((number ?? 0) + 1)" + cell.delegate = self + return cell + } + + func didUpdate(to object: Any) { + number = object as? Int + } + + func didSelectItem(at index: Int) {} + + //MARK: RemoveCellDelegate + + func removeCellDidTapButton(_ cell: RemoveCell) { + delegate?.removeSectionControllerWantsRemoved(self) + } + +} diff --git a/Example/IGListKitExamples/SectionControllers/SearchSectionController.swift b/Example/IGListKitExamples/SectionControllers/SearchSectionController.swift new file mode 100644 index 000000000..14b74fc9b --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/SearchSectionController.swift @@ -0,0 +1,71 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import IGListKit + +protocol SearchSectionControllerDelegate: class { + func searchSectionController(_ sectionController: SearchSectionController, didChangeText text: String) +} + +class SearchSectionController: IGListSectionController, IGListSectionType, IGListDisplayDelegate, UISearchBarDelegate { + + weak var delegate: SearchSectionControllerDelegate? + + override init() { + super.init() + displayDelegate = self + } + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + return CGSize(width: collectionContext!.containerSize.width, height: 44) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCell(of: SearchCell.self, for: self, at: index) as! SearchCell + cell.searchBar.delegate = self + return cell + } + + func didUpdate(to object: Any) {} + func didSelectItem(at index: Int) {} + + //MARK: UISearchBarDelegate + + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + delegate?.searchSectionController(self, didChangeText: searchText) + } + + func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + delegate?.searchSectionController(self, didChangeText: "") + } + + //MARK: IGListDisplayDelegate + + func listAdapter(_ listAdapter: IGListAdapter, didScrollSectionController sectionController: IGListSectionController) { + if let searchBar = (collectionContext?.cellForItem(at: 0, sectionController: self) as? SearchCell)?.searchBar { + searchBar.text = "" + searchBar.resignFirstResponder() + } + } + + func listAdapter(_ listAdapter: IGListAdapter, willDisplay sectionController: IGListSectionController) {} + func listAdapter(_ listAdapter: IGListAdapter, willDisplay sectionController: IGListSectionController, cell: UICollectionViewCell, at index: Int) {} + func listAdapter(_ listAdapter: IGListAdapter, didEndDisplaying sectionController: IGListSectionController) {} + func listAdapter(_ listAdapter: IGListAdapter, didEndDisplaying sectionController: IGListSectionController, cell: UICollectionViewCell, at index: Int) {} + +} diff --git a/Example/IGListKitExamples/SectionControllers/UserSectionController.swift b/Example/IGListKitExamples/SectionControllers/UserSectionController.swift new file mode 100644 index 000000000..eed891ded --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/UserSectionController.swift @@ -0,0 +1,43 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class UserSectionController: IGListSectionController, IGListSectionType { + + var user: User? + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + return CGSize(width: collectionContext!.containerSize.width, height: 55) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCell(of: DetailLabelCell.self, for: self, at: index) as! DetailLabelCell + cell.titleLabel.text = user?.name + cell.detailLabel.text = "@" + (user?.handle ?? "") + return cell + } + + func didUpdate(to object: Any) { + self.user = object as? User + } + + func didSelectItem(at index: Int) {} + +} diff --git a/Example/IGListKitExamples/ViewControllers/DemosViewController.swift b/Example/IGListKitExamples/ViewControllers/DemosViewController.swift new file mode 100644 index 000000000..b6d08d8a9 --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/DemosViewController.swift @@ -0,0 +1,60 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class DemosViewController: UIViewController, IGListAdapterDataSource { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout()) + + let demos: [DemoItem] = [ + DemoItem(name: "Tail Loading", controllerClass: LoadMoreViewController.self), + DemoItem(name: "Search Autocomplete", controllerClass: SearchViewController.self), + DemoItem(name: "Mixed Data", controllerClass: MixedDataViewController.self), + DemoItem(name: "Nested Adapter", controllerClass: NestedAdapterViewController.self), + DemoItem(name: "Empty View", controllerClass: EmptyViewController.self) + ] + + override func viewDidLoad() { + super.viewDidLoad() + title = "Demos" + view.addSubview(collectionView) + adapter.collectionView = collectionView + adapter.dataSource = self + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.frame = view.bounds + } + + //MARK: IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + return demos + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + return DemoSectionController() + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { + return nil + } + +} diff --git a/Example/IGListKitExamples/ViewControllers/EmptyViewController.swift b/Example/IGListKitExamples/ViewControllers/EmptyViewController.swift new file mode 100644 index 000000000..ba719f4fe --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/EmptyViewController.swift @@ -0,0 +1,91 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class EmptyViewController: UIViewController, IGListAdapterDataSource, RemoveSectionControllerDelegate { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout()) + + let emptyLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + label.text = "No more data!" + label.backgroundColor = UIColor.clear + return label + }() + + var tally = 4 + var data = [ + 1, + 2, + 3, + 4 + ] + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, + target: self, + action: #selector(EmptyViewController.onAdd)) + + collectionView.backgroundColor = UIColor(white: 0.9, alpha: 1) + view.addSubview(collectionView) + adapter.collectionView = collectionView + adapter.dataSource = self + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.frame = view.bounds + } + + func onAdd() { + data.append(tally + 1) + tally += 1 + adapter.performUpdates(animated: true, completion: nil) + } + + //MARK: IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + return data as [IGListDiffable] + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + let sectionController = RemoveSectionController() + sectionController.delegate = self + return sectionController + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { + return emptyLabel + } + + //MARK: RemoveSectionControllerDelegate + + func removeSectionControllerWantsRemoved(_ sectionController: RemoveSectionController) { + let section = adapter.section(for: sectionController) + guard let object = adapter.object(atSection: section) as? Int, let index = data.index(of: object) else { return } + data.remove(at: index) + adapter.performUpdates(animated: true, completion: nil) + } + +} diff --git a/Example/IGListKitExamples/ViewControllers/LoadMoreViewController.swift b/Example/IGListKitExamples/ViewControllers/LoadMoreViewController.swift new file mode 100644 index 000000000..8209267e1 --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/LoadMoreViewController.swift @@ -0,0 +1,81 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class LoadMoreViewController: UIViewController, IGListAdapterDataSource, UIScrollViewDelegate { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout()) + + lazy var words = "Maecenas faucibus mollis interdum Praesent commodo cursus magna, vel scelerisque nisl consectetur et".components(separatedBy: " ") + var loading = false + let spinToken = NSObject() + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(collectionView) + adapter.collectionView = collectionView + adapter.dataSource = self + adapter.scrollViewDelegate = self + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.frame = view.bounds + } + + //MARK: IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + var items: [IGListDiffable] = words as [IGListDiffable] + if loading { + items.append(spinToken) + } + return items + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + if let obj = object as? NSObject, obj === spinToken { + return spinnerSectionController() + } else { + return LabelSectionController() + } + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil } + + //MARK: UIScrollViewDelegate + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + let distance = scrollView.contentSize.height - (targetContentOffset.pointee.y + scrollView.bounds.height) + if !loading && distance < 200 { + loading = true + adapter.performUpdates(animated: true, completion: nil) + DispatchQueue.global(qos: .default).async(execute: { + // fake background loading task + sleep(2) + DispatchQueue.main.async { + self.loading = false + self.words.append(contentsOf: "Etiam porta sem malesuada magna mollis euismod".components(separatedBy: " ")) + self.adapter.performUpdates(animated: true, completion: nil) + } + }) + } + } + +} diff --git a/Example/IGListKitExamples/ViewControllers/MixedDataViewController.swift b/Example/IGListKitExamples/ViewControllers/MixedDataViewController.swift new file mode 100644 index 000000000..8b8b6998d --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/MixedDataViewController.swift @@ -0,0 +1,95 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class MixedDataViewController: UIViewController, IGListAdapterDataSource { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout()) + + let data = [ + "Maecenas faucibus mollis interdum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.", + GridItem(color: UIColor(red: 237/255.0, green: 73/255.0, blue: 86/255.0, alpha: 1), itemCount: 6), + User(pk: 2, name: "Ryan Olson", handle: "jessie_squires"), + "Praesent commodo cursus magna, vel scelerisque nisl consectetur et.", + User(pk: 4, name: "Oliver Rickard", handle: "ocrickard"), + GridItem(color: UIColor(red: 56/255.0, green: 151/255.0, blue: 240/255.0, alpha: 1), itemCount: 5), + "Nullam quis risus eget urna mollis ornare vel eu leo. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.", + User(pk: 3, name: "Jessie Squires", handle: "ryanolson"), + GridItem(color: UIColor(red: 112/255.0, green: 192/255.0, blue: 80/255.0, alpha: 1), itemCount: 3), + "Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.", + GridItem(color: UIColor(red: 163/255.0, green: 42/255.0, blue: 186/255.0, alpha: 1), itemCount: 7), + User(pk: 1, name: "Ryan Nystrom", handle: "_ryannystrom"), + ] as [Any] + + let segments: [(String, Any.Type?)] = [ + ("All", nil), + ("Colors", GridItem.self), + ("Text", String.self), + ("Users", User.self) + ] + var selectedClass: Any.Type? + + override func viewDidLoad() { + super.viewDidLoad() + + let control = UISegmentedControl(items: segments.map { return $0.0 } ) + control.selectedSegmentIndex = 0 + control.addTarget(self, action: #selector(MixedDataViewController.onControl(_:)), for: .valueChanged) + navigationItem.titleView = control + + view.addSubview(collectionView) + adapter.collectionView = collectionView + adapter.dataSource = self + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.frame = view.bounds + } + + func onControl(_ control: UISegmentedControl) { + selectedClass = segments[control.selectedSegmentIndex].1 + adapter.performUpdates(animated: true, completion: nil) + } + + //MARK: IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + var items = [IGListDiffable]() + for obj in data { + if selectedClass == nil || type(of: obj) == selectedClass! { + items.append(obj as! IGListDiffable) + } + } + return items + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + if let _ = object as? String { + return ExpandableSectionController() + } else if let _ = object as? GridItem { + return GridSectionController() + } else { + return UserSectionController() + } + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil } + +} diff --git a/Example/IGListKitExamples/ViewControllers/NestedAdapterViewController.swift b/Example/IGListKitExamples/ViewControllers/NestedAdapterViewController.swift new file mode 100644 index 000000000..21e9775b9 --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/NestedAdapterViewController.swift @@ -0,0 +1,66 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class NestedAdapterViewController: UIViewController, IGListAdapterDataSource { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout()) + + let data = [ + "Ridiculus Elit Tellus Purus Aenean", + "Condimentum Sollicitudin Adipiscing", + 14, + "Ligula Ipsum Tristique Parturient Euismod", + "Purus Dapibus Vulputate", + 6, + "Tellus Nibh Ipsum Inceptos", + 2 + ] as [Any] + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(collectionView) + adapter.collectionView = collectionView + adapter.dataSource = self + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.frame = view.bounds + } + + //MARK: IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + return data as! [IGListDiffable] + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + if let _ = object as? Int { + return HorizontalSectionController() + } else { + return LabelSectionController() + } + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { + return nil + } + +} diff --git a/Example/IGListKitExamples/ViewControllers/SearchViewController.swift b/Example/IGListKitExamples/ViewControllers/SearchViewController.swift new file mode 100644 index 000000000..30ee97a59 --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/SearchViewController.swift @@ -0,0 +1,81 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class SearchViewController: UIViewController, IGListAdapterDataSource, SearchSectionControllerDelegate { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout()) + lazy var words: [String] = { + let str = "Humblebrag skateboard tacos viral small batch blue bottle, schlitz fingerstache etsy squid. Listicle tote bag helvetica XOXO literally, meggings cardigan kickstarter roof party deep v selvage scenester venmo truffaut. You probably haven't heard of them fanny pack austin next level 3 wolf moon. Everyday carry offal brunch 8-bit, keytar banjo pinterest leggings hashtag wolf raw denim butcher. Single-origin coffee try-hard echo park neutra, cornhole banh mi meh austin readymade tacos taxidermy pug tattooed. Cold-pressed +1 ethical, four loko cardigan meh forage YOLO health goth sriracha kale chips. Mumblecore cardigan humblebrag, lo-fi typewriter truffaut leggings health goth." + var words = [String]() + let range = str.startIndex ..< str.endIndex + str.enumerateSubstrings(in: range, options: .byWords, { (substring, _, _, _) in + words.append(substring!) + }) + return words + }() + var filterString = "" + let searchToken = NSObject() + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(collectionView) + adapter.collectionView = collectionView + adapter.dataSource = self + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.frame = view.bounds + } + + //MARK: IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + var items: [IGListDiffable] = [searchToken] + for word in words { + if filterString == "" || word.lowercased().contains(filterString.lowercased()) { + items.append(word as IGListDiffable) + } + } + return items + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + if let obj = object as? NSObject, obj === searchToken { + let sectionController = SearchSectionController() + sectionController.delegate = self + return sectionController + } else { + return LabelSectionController() + } + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { + return nil + } + + //MARK: SearchSectionControllerDelegate + + func searchSectionController(_ sectionController: SearchSectionController, didChangeText text: String) { + filterString = text + adapter.performUpdates(animated: true, completion: nil) + } + +} diff --git a/Example/IGListKitExamples/Views/CenterLabelCell.swift b/Example/IGListKitExamples/Views/CenterLabelCell.swift new file mode 100644 index 000000000..1295af8c0 --- /dev/null +++ b/Example/IGListKitExamples/Views/CenterLabelCell.swift @@ -0,0 +1,34 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit + +class CenterLabelCell: UICollectionViewCell { + + lazy var label: UILabel = { + let view = UILabel() + view.backgroundColor = UIColor.clear + view.textAlignment = .center + view.textColor = UIColor.white + view.font = UIFont.boldSystemFont(ofSize: 18) + self.contentView.addSubview(view) + return view + }() + + override func layoutSubviews() { + super.layoutSubviews() + label.frame = contentView.bounds + } + +} diff --git a/Example/IGListKitExamples/Views/DetailLabelCell.swift b/Example/IGListKitExamples/Views/DetailLabelCell.swift new file mode 100644 index 000000000..067ccc080 --- /dev/null +++ b/Example/IGListKitExamples/Views/DetailLabelCell.swift @@ -0,0 +1,48 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit + +class DetailLabelCell: UICollectionViewCell { + + fileprivate let padding: CGFloat = 15.0 + + lazy var titleLabel: UILabel = { + let view = UILabel() + view.backgroundColor = UIColor.clear + view.textAlignment = .left + view.font = UIFont.systemFont(ofSize: 17) + view.textColor = UIColor.darkText + self.contentView.addSubview(view) + return view + }() + + lazy var detailLabel: UILabel = { + let view = UILabel() + view.backgroundColor = UIColor.clear + view.textAlignment = .right + view.font = UIFont.systemFont(ofSize: 17) + view.textColor = UIColor.lightGray + self.contentView.addSubview(view) + return view + }() + + override func layoutSubviews() { + super.layoutSubviews() + let frame = contentView.bounds.insetBy(dx: padding, dy: 0) + titleLabel.frame = frame + detailLabel.frame = frame + } + +} diff --git a/Example/IGListKitExamples/Views/EmbeddedCollectionViewCell.swift b/Example/IGListKitExamples/Views/EmbeddedCollectionViewCell.swift new file mode 100644 index 000000000..a2c47c4e9 --- /dev/null +++ b/Example/IGListKitExamples/Views/EmbeddedCollectionViewCell.swift @@ -0,0 +1,36 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +class EmbeddedCollectionViewCell: UICollectionViewCell { + + lazy var collectionView: IGListCollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + let view = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: layout) + view.backgroundColor = UIColor.clear + view.alwaysBounceVertical = false + view.alwaysBounceHorizontal = true + self.contentView.addSubview(view) + return view + }() + + override func layoutSubviews() { + super.layoutSubviews() + collectionView.frame = contentView.frame + } + +} diff --git a/Example/IGListKitExamples/Views/LabelCell.swift b/Example/IGListKitExamples/Views/LabelCell.swift new file mode 100644 index 000000000..8429c479d --- /dev/null +++ b/Example/IGListKitExamples/Views/LabelCell.swift @@ -0,0 +1,65 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit + +class LabelCell: UICollectionViewCell { + + fileprivate static let insets = UIEdgeInsets(top: 8, left: 15, bottom: 8, right: 15) + fileprivate static let font = UIFont.systemFont(ofSize: 17) + + static var singleLineHeight: CGFloat { + return font.lineHeight + insets.top + insets.bottom + } + + static func textHeight(_ text: String, width: CGFloat) -> CGFloat { + let constrainedSize = CGSize(width: width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude) + let attributes = [ NSFontAttributeName: font ] + let options: NSStringDrawingOptions = [.usesFontLeading, .usesLineFragmentOrigin] + let bounds = (text as NSString).boundingRect(with: constrainedSize, options: options, attributes: attributes, context: nil) + return ceil(bounds.height) + insets.top + insets.bottom + } + + lazy var label: UILabel = { + let label = UILabel() + label.backgroundColor = UIColor.clear + label.numberOfLines = 1 + label.font = LabelCell.font + self.contentView.addSubview(label) + return label + }() + + lazy var separator: CALayer = { + let layer = CALayer() + layer.backgroundColor = UIColor(red: 200/255.0, green: 199/255.0, blue: 204/255.0, alpha: 1).cgColor + self.contentView.layer.addSublayer(layer) + return layer + }() + + override func layoutSubviews() { + super.layoutSubviews() + let bounds = contentView.bounds + label.frame = UIEdgeInsetsInsetRect(bounds, LabelCell.insets) + let height: CGFloat = 0.5 + let left = LabelCell.insets.left + separator.frame = CGRect(x: left, y: bounds.height - height, width: bounds.width - left, height: height) + } + + override var isHighlighted: Bool { + didSet { + contentView.backgroundColor = UIColor(white: isHighlighted ? 0.9 : 1, alpha: 1) + } + } + +} diff --git a/Example/IGListKitExamples/Views/RemoveCell.swift b/Example/IGListKitExamples/Views/RemoveCell.swift new file mode 100644 index 000000000..f08924747 --- /dev/null +++ b/Example/IGListKitExamples/Views/RemoveCell.swift @@ -0,0 +1,55 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit + +protocol RemoveCellDelegate: class { + func removeCellDidTapButton(_ cell: RemoveCell) +} + +class RemoveCell: UICollectionViewCell { + + weak var delegate: RemoveCellDelegate? + + lazy var label: UILabel = { + let label = UILabel() + label.backgroundColor = UIColor.clear + self.contentView.addSubview(label) + return label + }() + + fileprivate lazy var button: UIButton = { + let button = UIButton(type: .custom) + button.setTitle("Remove", for: UIControlState()) + button.setTitleColor(UIColor.blue, for: UIControlState()) + button.backgroundColor = UIColor.clear + button.addTarget(self, action: #selector(RemoveCell.onButton(_:)), for: .touchUpInside) + self.contentView.addSubview(button) + return button + }() + + override func layoutSubviews() { + super.layoutSubviews() + contentView.backgroundColor = UIColor.white + let bounds = contentView.bounds + let divide = bounds.divided(atDistance: 100, from: .maxXEdge) + label.frame = divide.slice.insetBy(dx: 15, dy: 0) + button.frame = divide.remainder + } + + func onButton(_ button: UIButton) { + delegate?.removeCellDidTapButton(self) + } + +} diff --git a/Example/IGListKitExamples/Views/SearchCell.swift b/Example/IGListKitExamples/Views/SearchCell.swift new file mode 100644 index 000000000..dc3d39f79 --- /dev/null +++ b/Example/IGListKitExamples/Views/SearchCell.swift @@ -0,0 +1,30 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit + +class SearchCell: UICollectionViewCell { + + lazy var searchBar: UISearchBar = { + let view = UISearchBar() + self.contentView.addSubview(view) + return view + }() + + override func layoutSubviews() { + super.layoutSubviews() + searchBar.frame = contentView.bounds + } + +} diff --git a/Example/IGListKitExamples/Views/SpinnerCell.swift b/Example/IGListKitExamples/Views/SpinnerCell.swift new file mode 100644 index 000000000..04c55b6fc --- /dev/null +++ b/Example/IGListKitExamples/Views/SpinnerCell.swift @@ -0,0 +1,41 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + 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 + FACEBOOK 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. + */ + +import UIKit +import IGListKit + +func spinnerSectionController() -> IGListSingleSectionController { + let configureBlock = { (item: Any, cell: UICollectionViewCell) in} + let sizeBlock = { (context: IGListCollectionContext) -> CGSize in + return CGSize(width: context.containerSize.width, height: 100) + } + return IGListSingleSectionController(cellClass: SpinnerCell.self, configureBlock: configureBlock, sizeBlock: sizeBlock) +} + +class SpinnerCell: UICollectionViewCell { + + lazy var activityIndicator: UIActivityIndicatorView = { + let view = UIActivityIndicatorView(activityIndicatorStyle: .gray) + view.startAnimating() + self.contentView.addSubview(view) + return view + }() + + override func layoutSubviews() { + super.layoutSubviews() + let bounds = contentView.bounds + activityIndicator.center = CGPoint(x: bounds.midX, y: bounds.midY) + } + +} diff --git a/Example/LICENSE-examples.md b/Example/LICENSE-examples.md new file mode 100644 index 000000000..7d2d8e6ac --- /dev/null +++ b/Example/LICENSE-examples.md @@ -0,0 +1,11 @@ +Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + +The examples provided by Facebook are for non-commercial testing and evaluation +purposes only. Facebook reserves all rights not expressly granted. + +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 +FACEBOOK 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. \ No newline at end of file diff --git a/Example/Podfile b/Example/Podfile new file mode 100644 index 000000000..6f869695f --- /dev/null +++ b/Example/Podfile @@ -0,0 +1,5 @@ +use_frameworks! + +target 'IGListKitExamples' do + pod 'IGListKit', :path => '../IGListKit.podspec' +end \ No newline at end of file diff --git a/Example/Podfile.lock b/Example/Podfile.lock new file mode 100644 index 000000000..9a00e6256 --- /dev/null +++ b/Example/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - IGListKit (1.0) + +DEPENDENCIES: + - IGListKit (from `../IGListKit.podspec`) + +EXTERNAL SOURCES: + IGListKit: + :path: ../IGListKit.podspec + +SPEC CHECKSUMS: + IGListKit: 67493f1f775fea949596c7fc8317c7bd77ba8539 + +PODFILE CHECKSUM: 4bdfb42d1e7b2b121bf8c83bc300d7d77aa0fc3a + +COCOAPODS: 1.0.1 diff --git a/Example/Pods/Local Podspecs/IGListKit.podspec.json b/Example/Pods/Local Podspecs/IGListKit.podspec.json new file mode 100644 index 000000000..6c011ca13 --- /dev/null +++ b/Example/Pods/Local Podspecs/IGListKit.podspec.json @@ -0,0 +1,29 @@ +{ + "name": "IGListKit", + "version": "1.0", + "summary": "A data-driven UICollectionView framework.", + "homepage": "https://github.com/Instagram/IGListKit", + "documentation_url": "TODO", + "description": "Create data-driven feeds backed by UICollectionView that efficiently diff and update.", + "license": { + "type": "BSD" + }, + "authors": "Instagram", + "social_media_url": "https://twitter.com/fbOpenSource", + "source": { + "git": "https://github.com/Instagram/IGListKit.git", + "tag": "1.0" + }, + "source_files": "Source/**/*.{h,m,mm}", + "private_header_files": "Source/Internal/*.h", + "requires_arc": true, + "platforms": { + "ios": "8.0" + }, + "frameworks": "UIKit", + "libraries": "c++", + "pod_target_xcconfig": { + "CLANG_CXX_LANGUAGE_STANDARD": "c++11", + "CLANG_CXX_LIBRARY": "libc++" + } +} diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock new file mode 100644 index 000000000..9a00e6256 --- /dev/null +++ b/Example/Pods/Manifest.lock @@ -0,0 +1,16 @@ +PODS: + - IGListKit (1.0) + +DEPENDENCIES: + - IGListKit (from `../IGListKit.podspec`) + +EXTERNAL SOURCES: + IGListKit: + :path: ../IGListKit.podspec + +SPEC CHECKSUMS: + IGListKit: 67493f1f775fea949596c7fc8317c7bd77ba8539 + +PODFILE CHECKSUM: 4bdfb42d1e7b2b121bf8c83bc300d7d77aa0fc3a + +COCOAPODS: 1.0.1 diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 000000000..96dd5915f --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,786 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0147435D65030952AE49DEC4E017B23D /* UICollectionView+IGListBatchUpdateData.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E99717CDC2053D36C6789D1148FB800 /* UICollectionView+IGListBatchUpdateData.m */; }; + 026755843D7FB53E35679B703EFBBF6E /* IGListSectionMap.m in Sources */ = {isa = PBXBuildFile; fileRef = BA166501E81C4BA1DF5A400979E86CDB /* IGListSectionMap.m */; }; + 084B851CFFA9C0E2DF68D903E53E0EC8 /* IGListAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = 5317DB3BAAEE0CC7B2D2B105A879060D /* IGListAssert.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0C27A4BED7FE0607F8E864854B0D4CB4 /* IGListIndexPathResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7377305092E6A1E16624C7EBA4782FED /* IGListIndexPathResultInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 135EAE77737104157F6CC0655BBA0ABA /* IGListSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = F5AD8343AC8B755A3258D6E45FFD2D8A /* IGListSectionController.m */; }; + 1691094821EAD389A7A9C0BA79A970DB /* IGListIndexSetResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = B4D067AEE0F5C07151A19715FCE108F3 /* IGListIndexSetResultInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 171501700F6A914CD4C44E3666398BB5 /* IGListAdapterProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E2D3628C46D46E7D75C2D9C65C0C6E7 /* IGListAdapterProxy.m */; }; + 1CCB2FEACC2123958F5A0E39F70659B5 /* IGListDisplayHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = D3934C6500E5C7C907F1AC3B6C1AF15C /* IGListDisplayHandler.m */; }; + 1EB6675BEF69B9E921CE4199DD7156D0 /* IGListAdapterInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DC277CBFF0D26310BEA0C5603C4EAA45 /* IGListAdapterInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 1FD624CB427ECAEF7F1F9949E4B023AE /* IGListUpdatingDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 5074228D8F3A063BF5EE38BBCDC854D8 /* IGListUpdatingDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 213FF484E5786DD35F2A932774C57707 /* IGListSingleSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 91007FC1F36EE50D4609909509B4BE04 /* IGListSingleSectionController.m */; }; + 25FDCBDCEA5575091B36BFE4B59D04CA /* IGListDiff.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14381CF1C8B474F904BED10858A3B1 /* IGListDiff.mm */; }; + 26750C29F741D0EF22EF396017265DC7 /* IGListSupplementaryViewSource.h in Headers */ = {isa = PBXBuildFile; fileRef = D7D841899F69CAEFE9CC75F4C742EB7F /* IGListSupplementaryViewSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 28063C0E5F9119F809C37919575A132F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2A6FF208CEC4C2861F1E9D42D651453 /* Foundation.framework */; }; + 2912A571DF503B64C589EC9A44BA4B39 /* IGListIndexPathResult.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1EB6F6A5F5CF2DA79688B3A01F95BC /* IGListIndexPathResult.m */; }; + 2C58ED7F6EFFB13891EBAAF768E2C998 /* IGListSingleSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = A6A48D5B55D7631327C124F538C6E068 /* IGListSingleSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32DA59CCC51F3C4A5239E87A45351EEE /* IGListMoveIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F401C4ED6FD8A5953382E94BFDC1B04 /* IGListMoveIndexPath.m */; }; + 35102F69615549F427FE2EC453149DAB /* IGListAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = 73259AA7FEA14577AEF2DB550C3020EB /* IGListAdapter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 419BBEB1F205EA82FF1A300B6E954F7D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2A6FF208CEC4C2861F1E9D42D651453 /* Foundation.framework */; }; + 444D6A0E181DF93311717CA41DB7B48A /* IGListMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E7FEF0C49C387ED472744AB5E63D5C5 /* IGListMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4C6CC1B183686EB9EE4A7FF40CAFF697 /* IGListCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B5597025F567F3628A6FE6AB07D53F3 /* IGListCollectionView.m */; }; + 4CCFD8A322D6F78F55604A0E638FE191 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E86668F20F252C440D1015B816BE59C5 /* UIKit.framework */; }; + 503BDC35E6053B83D862316F3D8C935F /* IGListDiff.h in Headers */ = {isa = PBXBuildFile; fileRef = B3A4D0DCC742ED05F9B0EAD98B483829 /* IGListDiff.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5455F7C71EB93A534D8A09AAFE8EF0FE /* IGListBatchUpdateData.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0AEE94B91F9849EEF961CC1C265C985 /* IGListBatchUpdateData.mm */; }; + 54CFA8A4442E7B0BB4C67FE811481E4C /* IGListMoveIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = D37ACDCF54F63073142D7196E56946A3 /* IGListMoveIndex.m */; }; + 54E0CADE21353023E6F071C5FB164902 /* IGListStackedSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 22F3AD163D43B95418E4F734FDD061F4 /* IGListStackedSectionController.m */; }; + 54EAA3B5C88905CD01B5FE2ADDF92FC6 /* IGListMoveIndexPathInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D8C265CA74986A39B9EF8127AE0349D /* IGListMoveIndexPathInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 57FF7C6AED54D8A04C41DA36231BFC94 /* IGListStackedSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = B9B2C760A6B8BC4E2F9B9BC221585A06 /* IGListStackedSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6110D5294C734F5AF8FAF5AD4134789F /* IGListSectionControllerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 928F8C884AA9FD0FBF674F4CF5DAC4D8 /* IGListSectionControllerInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 623527A4232D59FB38547902423B5AA2 /* IGListCollectionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 17FC1649F3BE2A7A920FF0C7D50B7EC2 /* IGListCollectionContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68A93F0E164CD1E9910C25698B5B0DC7 /* IGListAdapterProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 109159D72077E46327880D41EE69B804 /* IGListAdapterProxy.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6B2EE19479243F984CE51F15ACCB1A33 /* IGListAdapterDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = FE4F9D5E24C8E166CBE1324FB4EFE901 /* IGListAdapterDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6C43A407033E5A273E9E16EE7E334EB2 /* IGListSectionType.h in Headers */ = {isa = PBXBuildFile; fileRef = C0D34FDB422AF09AA6F7813D34DA3DCD /* IGListSectionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6F06EF99F8A0B55F4E81D767EB7F4B36 /* IGListMoveIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = A25B427B6865597BD60B14637B4DF317 /* IGListMoveIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 725B0E02B9D828F61443072352992F54 /* IGListWorkingRangeHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0103D32BB7A0A6165DCA97AE90B78FF3 /* IGListWorkingRangeHandler.mm */; }; + 76B403D7072D98BED2AA6AA6C7827E39 /* IGListAdapterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C9AC4FA89437FC74B5044CB6E09F330 /* IGListAdapterDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 80AB64741918F4F2A7585C3C5FCBD98D /* IGListCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FE4AA45BC45A8EF0F9FB575B19EE801 /* IGListCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 83E3217B930A28021BC1DFC1DFD759A0 /* IGListWorkingRangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F947E7710011A32E4FA3C988472760 /* IGListWorkingRangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 86B79ED8B51A8D5353E49A6C37DFE5CB /* IGListMoveIndexInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ADB9063D305B8288C4BB24BC56A463F /* IGListMoveIndexInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 86BA72487680B4F3CFEB719F821D183A /* IGListWorkingRangeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F1AA350148ECB86D3C7EAA99E50E71E /* IGListWorkingRangeHandler.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 8F026EF7A1AB975A5411D2BDE5B4C081 /* IGListMoveIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F331B7F935DA82F2BA8F9EC9CDAC989 /* IGListMoveIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 931AB3AD8B295F26EDDB6FC6849EA247 /* IGListIndexSetResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 84752D2DDFC17301AD69B035A1ABEB98 /* IGListIndexSetResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9368BAE80054E3977919DCC164581318 /* UICollectionView+IGListBatchUpdateData.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F7D2957E3E38FF3DDE6BDE4FB7B5599 /* UICollectionView+IGListBatchUpdateData.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9EA7B904CC4B3E23C79B9B3909A51C2A /* IGListIndexPathResult.h in Headers */ = {isa = PBXBuildFile; fileRef = DBA04B01FE70BC1FC9B2AF5A04816653 /* IGListIndexPathResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9F9C5D1351A6EB88531F759304A7A5D6 /* IGListSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = 50D66D11EB993B3B287DA0B8DEE153B7 /* IGListSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A01D6F5340CA1BD84E5DAB81B60A02A2 /* IGListDiffable.h in Headers */ = {isa = PBXBuildFile; fileRef = 54A169D9E10ABA4A657E466A81CF5E96 /* IGListDiffable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6091B575605FDBAD5E9E5645248FD66 /* IGListAdapterUpdaterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 923F1BE703C78DB09D0F325460A4A2EA /* IGListAdapterUpdaterDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B01F1324E6269028CE385E78C26EF9D3 /* IGListDisplayHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 0446A9446334650F8C62E86A0E6E53A3 /* IGListDisplayHandler.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B17B8BA5347B0717A112EB2BCE055293 /* IGListExperiments.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CFC4E4785DC5CD494D3BA69EE0EF1C5 /* IGListExperiments.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B70560D9E6761A6E5666060E05DE071C /* IGListStackedSectionControllerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DD4F28A7CC1A638EF87DA9D5583C4CAA /* IGListStackedSectionControllerInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B7FADDD58154CEA589481E450BD88E33 /* Pods-IGListKitExamples-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F5AE0385534EC7E6CA75003A7280492 /* Pods-IGListKitExamples-dummy.m */; }; + BA98207EA92AA990BE64F3C8970CBC9A /* NSObject+IGListDiffable.m in Sources */ = {isa = PBXBuildFile; fileRef = 720FAF43FD40FA27160DB53AD4C8E73D /* NSObject+IGListDiffable.m */; }; + BAB517802021813C296E2F2BA2C91FE2 /* IGListAdapterUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = 60379E52F4C982E1C790B6519E71C7C7 /* IGListAdapterUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BC0126520E73770835746D3B1B613938 /* IGListAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACF94F01EB36325B7E6582A24CE67E1 /* IGListAdapter.m */; }; + BF318B0B45ACAF2725C9AC468D256E3D /* IGListSectionMap.h in Headers */ = {isa = PBXBuildFile; fileRef = D6F285EE404C699C0CC7C31E7245B307 /* IGListSectionMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; + BF4804E0DD82EA483FF3C82F8DFCABBD /* IGListAdapterUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 64E8ACC5C6782B94FD1B95596927A543 /* IGListAdapterUpdater.m */; }; + BFEEC2F47FC552E9E6EC80C984A8DE10 /* NSIndexSet+PrettyDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 5258E047034F850DB89896B38AC9EDB2 /* NSIndexSet+PrettyDescription.h */; settings = {ATTRIBUTES = (Private, ); }; }; + C4328DE47976156C06FB51E1FC1C79FA /* IGListDisplayDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E2B529498914743C310A5BF137656A18 /* IGListDisplayDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C47B57F6C4A0B47A302C95C9FBD5BFDE /* IGListKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 85FB0BDA4A7F33F3588C88434ECD2E94 /* IGListKit-dummy.m */; }; + C7B337BF99A703262B38856C2A916613 /* NSObject+IGListDiffable.h in Headers */ = {isa = PBXBuildFile; fileRef = 516F4D39676D39DB68711DFCCF99CD08 /* NSObject+IGListDiffable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCCD28F400F07B8C22AC80757491E13B /* IGListAdapterUpdaterInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = D922528E54D29F20913750659CF4DD3E /* IGListAdapterUpdaterInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + D46249EFD12F9C350961FEA1831DF20F /* IGListBatchUpdateData.h in Headers */ = {isa = PBXBuildFile; fileRef = BE4F9093FE09B54A0DC32E8161904775 /* IGListBatchUpdateData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DBE42210054F8FDA69270F4EC611FD4C /* IGListScrollDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = A48E8E2621D1D301DF15DC1EA7F25CB7 /* IGListScrollDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E01ABFB55E486E56D309DE5BAA801876 /* IGListKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 82BBB8E0984E89E3CD3311E21D7F65D6 /* IGListKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EA5AEB7F2DADFF1BA74A38860AA60BA5 /* IGListIndexSetResult.m in Sources */ = {isa = PBXBuildFile; fileRef = B281B0B7972DE5D26848178203A718AE /* IGListIndexSetResult.m */; }; + F360C686D507AA39A221B314C92050B1 /* IGListKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DD75371720A47691326032C3842EA7D /* IGListKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F5383C2327F50B07F0904BFE6EB496C8 /* IGListReloadDataUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = EBFD4BC90748BE8DE367B6DCA73B982A /* IGListReloadDataUpdater.m */; }; + F7023388DF86747C3752D4BCFFD9BE98 /* NSIndexSet+PrettyDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 380BD8B88A62F76284458B6E06908A47 /* NSIndexSet+PrettyDescription.m */; }; + FD7CE90CC29F48232D88E3273B183752 /* IGListReloadDataUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA049308F02171E1FE2541EFF16E527 /* IGListReloadDataUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF9FF8C3E7C149B40D25B5B2BB530C27 /* Pods-IGListKitExamples-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 52054AE893A0AE7FBFC54333D8ABD345 /* Pods-IGListKitExamples-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 5A4D93DEC315EA1419504908E8F1310B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 26932F09684B706888A7BF05E69D387C; + remoteInfo = IGListKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0103D32BB7A0A6165DCA97AE90B78FF3 /* IGListWorkingRangeHandler.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = IGListWorkingRangeHandler.mm; sourceTree = ""; }; + 0446A9446334650F8C62E86A0E6E53A3 /* IGListDisplayHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListDisplayHandler.h; sourceTree = ""; }; + 109159D72077E46327880D41EE69B804 /* IGListAdapterProxy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAdapterProxy.h; sourceTree = ""; }; + 17FC1649F3BE2A7A920FF0C7D50B7EC2 /* IGListCollectionContext.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListCollectionContext.h; sourceTree = ""; }; + 1B2B14BA3AB402D9CC387EEB27A2F746 /* Pods-IGListKitExamples.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-IGListKitExamples.debug.xcconfig"; sourceTree = ""; }; + 1DD75371720A47691326032C3842EA7D /* IGListKit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListKit.h; sourceTree = ""; }; + 1FE4AA45BC45A8EF0F9FB575B19EE801 /* IGListCollectionView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListCollectionView.h; sourceTree = ""; }; + 22F3AD163D43B95418E4F734FDD061F4 /* IGListStackedSectionController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListStackedSectionController.m; sourceTree = ""; }; + 2F5AE0385534EC7E6CA75003A7280492 /* Pods-IGListKitExamples-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-IGListKitExamples-dummy.m"; sourceTree = ""; }; + 380BD8B88A62F76284458B6E06908A47 /* NSIndexSet+PrettyDescription.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "NSIndexSet+PrettyDescription.m"; sourceTree = ""; }; + 428C13C7C57B2D91F00D94ECF1FA71F8 /* Pods-IGListKitExamples-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-IGListKitExamples-resources.sh"; sourceTree = ""; }; + 4B14381CF1C8B474F904BED10858A3B1 /* IGListDiff.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = IGListDiff.mm; sourceTree = ""; }; + 4B5597025F567F3628A6FE6AB07D53F3 /* IGListCollectionView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListCollectionView.m; sourceTree = ""; }; + 4F1AA350148ECB86D3C7EAA99E50E71E /* IGListWorkingRangeHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListWorkingRangeHandler.h; sourceTree = ""; }; + 4F331B7F935DA82F2BA8F9EC9CDAC989 /* IGListMoveIndex.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListMoveIndex.h; sourceTree = ""; }; + 4F7D2957E3E38FF3DDE6BDE4FB7B5599 /* UICollectionView+IGListBatchUpdateData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+IGListBatchUpdateData.h"; sourceTree = ""; }; + 5074228D8F3A063BF5EE38BBCDC854D8 /* IGListUpdatingDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListUpdatingDelegate.h; sourceTree = ""; }; + 50D66D11EB993B3B287DA0B8DEE153B7 /* IGListSectionController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListSectionController.h; sourceTree = ""; }; + 516F4D39676D39DB68711DFCCF99CD08 /* NSObject+IGListDiffable.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "NSObject+IGListDiffable.h"; sourceTree = ""; }; + 52054AE893A0AE7FBFC54333D8ABD345 /* Pods-IGListKitExamples-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-IGListKitExamples-umbrella.h"; sourceTree = ""; }; + 5258E047034F850DB89896B38AC9EDB2 /* NSIndexSet+PrettyDescription.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+PrettyDescription.h"; sourceTree = ""; }; + 5317DB3BAAEE0CC7B2D2B105A879060D /* IGListAssert.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAssert.h; sourceTree = ""; }; + 54A169D9E10ABA4A657E466A81CF5E96 /* IGListDiffable.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListDiffable.h; sourceTree = ""; }; + 561D1CAC9AE6D43A06CCEAFEC05B04DA /* IGListKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = IGListKit.modulemap; sourceTree = ""; }; + 5B065D6991AF097908DF9824689FB9C4 /* IGListKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IGListKit-prefix.pch"; sourceTree = ""; }; + 60379E52F4C982E1C790B6519E71C7C7 /* IGListAdapterUpdater.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAdapterUpdater.h; sourceTree = ""; }; + 64E8ACC5C6782B94FD1B95596927A543 /* IGListAdapterUpdater.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterUpdater.m; sourceTree = ""; }; + 6ACF94F01EB36325B7E6582A24CE67E1 /* IGListAdapter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListAdapter.m; sourceTree = ""; }; + 6E2D3628C46D46E7D75C2D9C65C0C6E7 /* IGListAdapterProxy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterProxy.m; sourceTree = ""; }; + 720FAF43FD40FA27160DB53AD4C8E73D /* NSObject+IGListDiffable.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "NSObject+IGListDiffable.m"; sourceTree = ""; }; + 73259AA7FEA14577AEF2DB550C3020EB /* IGListAdapter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAdapter.h; sourceTree = ""; }; + 7377305092E6A1E16624C7EBA4782FED /* IGListIndexPathResultInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListIndexPathResultInternal.h; sourceTree = ""; }; + 7ADB9063D305B8288C4BB24BC56A463F /* IGListMoveIndexInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListMoveIndexInternal.h; sourceTree = ""; }; + 7E7FEF0C49C387ED472744AB5E63D5C5 /* IGListMacros.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListMacros.h; sourceTree = ""; }; + 7F401C4ED6FD8A5953382E94BFDC1B04 /* IGListMoveIndexPath.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListMoveIndexPath.m; sourceTree = ""; }; + 82BBB8E0984E89E3CD3311E21D7F65D6 /* IGListKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IGListKit-umbrella.h"; sourceTree = ""; }; + 84752D2DDFC17301AD69B035A1ABEB98 /* IGListIndexSetResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListIndexSetResult.h; sourceTree = ""; }; + 85FB0BDA4A7F33F3588C88434ECD2E94 /* IGListKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "IGListKit-dummy.m"; sourceTree = ""; }; + 891CE36E66A66905E25AD89B001B5648 /* IGListKit.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IGListKit.xcconfig; sourceTree = ""; }; + 8C9AC4FA89437FC74B5044CB6E09F330 /* IGListAdapterDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAdapterDelegate.h; sourceTree = ""; }; + 8CFC4E4785DC5CD494D3BA69EE0EF1C5 /* IGListExperiments.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListExperiments.h; sourceTree = ""; }; + 8D8C265CA74986A39B9EF8127AE0349D /* IGListMoveIndexPathInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListMoveIndexPathInternal.h; sourceTree = ""; }; + 91007FC1F36EE50D4609909509B4BE04 /* IGListSingleSectionController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListSingleSectionController.m; sourceTree = ""; }; + 923F1BE703C78DB09D0F325460A4A2EA /* IGListAdapterUpdaterDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAdapterUpdaterDelegate.h; sourceTree = ""; }; + 928F8C884AA9FD0FBF674F4CF5DAC4D8 /* IGListSectionControllerInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListSectionControllerInternal.h; sourceTree = ""; }; + 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9D11DCAE06F644E6CCE659622CD8E616 /* IGListKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IGListKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9E99717CDC2053D36C6789D1148FB800 /* UICollectionView+IGListBatchUpdateData.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+IGListBatchUpdateData.m"; sourceTree = ""; }; + A198E265B2C6E673C7C9C5050F92D9F0 /* Pods-IGListKitExamples.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-IGListKitExamples.release.xcconfig"; sourceTree = ""; }; + A25B427B6865597BD60B14637B4DF317 /* IGListMoveIndexPath.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListMoveIndexPath.h; sourceTree = ""; }; + A2A6FF208CEC4C2861F1E9D42D651453 /* 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; }; + A48E8E2621D1D301DF15DC1EA7F25CB7 /* IGListScrollDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListScrollDelegate.h; sourceTree = ""; }; + A6A48D5B55D7631327C124F538C6E068 /* IGListSingleSectionController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListSingleSectionController.h; sourceTree = ""; }; + A7BBE69D34859663403DA26F14CC4DDB /* Pods-IGListKitExamples-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-IGListKitExamples-acknowledgements.markdown"; sourceTree = ""; }; + A851A4ACB8C0DE98BFFBC6FD4D1BACEE /* Pods-IGListKitExamples-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-IGListKitExamples-frameworks.sh"; sourceTree = ""; }; + ABF549428FEA18696E5358F599E11A24 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B281B0B7972DE5D26848178203A718AE /* IGListIndexSetResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListIndexSetResult.m; sourceTree = ""; }; + B3A4D0DCC742ED05F9B0EAD98B483829 /* IGListDiff.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListDiff.h; sourceTree = ""; }; + B4D067AEE0F5C07151A19715FCE108F3 /* IGListIndexSetResultInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListIndexSetResultInternal.h; sourceTree = ""; }; + B9B2C760A6B8BC4E2F9B9BC221585A06 /* IGListStackedSectionController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListStackedSectionController.h; sourceTree = ""; }; + BA166501E81C4BA1DF5A400979E86CDB /* IGListSectionMap.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListSectionMap.m; sourceTree = ""; }; + BE4F9093FE09B54A0DC32E8161904775 /* IGListBatchUpdateData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListBatchUpdateData.h; sourceTree = ""; }; + BF8DCC2E99230AF598F30C8A54C525FB /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C0D34FDB422AF09AA6F7813D34DA3DCD /* IGListSectionType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListSectionType.h; sourceTree = ""; }; + C7F454644FEBF5DB647AE1728D1FD067 /* Pods_IGListKitExamples.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IGListKitExamples.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CDA049308F02171E1FE2541EFF16E527 /* IGListReloadDataUpdater.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListReloadDataUpdater.h; sourceTree = ""; }; + D0AEE94B91F9849EEF961CC1C265C985 /* IGListBatchUpdateData.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = IGListBatchUpdateData.mm; sourceTree = ""; }; + D37ACDCF54F63073142D7196E56946A3 /* IGListMoveIndex.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListMoveIndex.m; sourceTree = ""; }; + D3934C6500E5C7C907F1AC3B6C1AF15C /* IGListDisplayHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListDisplayHandler.m; sourceTree = ""; }; + D6F285EE404C699C0CC7C31E7245B307 /* IGListSectionMap.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListSectionMap.h; sourceTree = ""; }; + D7D841899F69CAEFE9CC75F4C742EB7F /* IGListSupplementaryViewSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListSupplementaryViewSource.h; sourceTree = ""; }; + D803D538BF4074498E5ADB84A16E0395 /* Pods-IGListKitExamples-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-IGListKitExamples-acknowledgements.plist"; sourceTree = ""; }; + D922528E54D29F20913750659CF4DD3E /* IGListAdapterUpdaterInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAdapterUpdaterInternal.h; sourceTree = ""; }; + D9F947E7710011A32E4FA3C988472760 /* IGListWorkingRangeDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListWorkingRangeDelegate.h; sourceTree = ""; }; + DBA04B01FE70BC1FC9B2AF5A04816653 /* IGListIndexPathResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListIndexPathResult.h; sourceTree = ""; }; + DC277CBFF0D26310BEA0C5603C4EAA45 /* IGListAdapterInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAdapterInternal.h; sourceTree = ""; }; + DD1EB6F6A5F5CF2DA79688B3A01F95BC /* IGListIndexPathResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListIndexPathResult.m; sourceTree = ""; }; + DD4F28A7CC1A638EF87DA9D5583C4CAA /* IGListStackedSectionControllerInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListStackedSectionControllerInternal.h; sourceTree = ""; }; + DDF922EBFB237C1B42E8B07306D987E8 /* Pods-IGListKitExamples.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-IGListKitExamples.modulemap"; sourceTree = ""; }; + E2B529498914743C310A5BF137656A18 /* IGListDisplayDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListDisplayDelegate.h; sourceTree = ""; }; + E86668F20F252C440D1015B816BE59C5 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + EBFD4BC90748BE8DE367B6DCA73B982A /* IGListReloadDataUpdater.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListReloadDataUpdater.m; sourceTree = ""; }; + F5AD8343AC8B755A3258D6E45FFD2D8A /* IGListSectionController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = IGListSectionController.m; sourceTree = ""; }; + FE4F9D5E24C8E166CBE1324FB4EFE901 /* IGListAdapterDataSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = IGListAdapterDataSource.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AA75830E6081F3DABCD8A9D38D8D20E7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 28063C0E5F9119F809C37919575A132F /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FE924FF264CD5BDB6DFE9C2ACA63FCAC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 419BBEB1F205EA82FF1A300B6E954F7D /* Foundation.framework in Frameworks */, + 4CCFD8A322D6F78F55604A0E638FE191 /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0461D34BBFD8CE6835409D3733A89161 /* IGListKit */ = { + isa = PBXGroup; + children = ( + F140F35915E040972E6D461E635BCD4C /* Source */, + DAB5B0D31F1F31F2EA5708FF996EE588 /* Support Files */, + ); + name = IGListKit; + path = ../..; + sourceTree = ""; + }; + 275D18F543186A905E745D345F66E093 /* Development Pods */ = { + isa = PBXGroup; + children = ( + 0461D34BBFD8CE6835409D3733A89161 /* IGListKit */, + ); + name = "Development Pods"; + sourceTree = ""; + }; + 433CD3331B6C3787F473C941B61FC68F /* Frameworks */ = { + isa = PBXGroup; + children = ( + F302CD3B306AF1ED96B9AF31FCDBC26C /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + 51A62FAD141FCC64FABDAA958233E728 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + F22E6673D29C1778B1984CF7D4D871E9 /* Pods-IGListKitExamples */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 568515C4F4B02B048840EBD06A1CC65D /* Internal */ = { + isa = PBXGroup; + children = ( + DC277CBFF0D26310BEA0C5603C4EAA45 /* IGListAdapterInternal.h */, + 109159D72077E46327880D41EE69B804 /* IGListAdapterProxy.h */, + 6E2D3628C46D46E7D75C2D9C65C0C6E7 /* IGListAdapterProxy.m */, + D922528E54D29F20913750659CF4DD3E /* IGListAdapterUpdaterInternal.h */, + 0446A9446334650F8C62E86A0E6E53A3 /* IGListDisplayHandler.h */, + D3934C6500E5C7C907F1AC3B6C1AF15C /* IGListDisplayHandler.m */, + 7377305092E6A1E16624C7EBA4782FED /* IGListIndexPathResultInternal.h */, + B4D067AEE0F5C07151A19715FCE108F3 /* IGListIndexSetResultInternal.h */, + 7ADB9063D305B8288C4BB24BC56A463F /* IGListMoveIndexInternal.h */, + 8D8C265CA74986A39B9EF8127AE0349D /* IGListMoveIndexPathInternal.h */, + 928F8C884AA9FD0FBF674F4CF5DAC4D8 /* IGListSectionControllerInternal.h */, + D6F285EE404C699C0CC7C31E7245B307 /* IGListSectionMap.h */, + BA166501E81C4BA1DF5A400979E86CDB /* IGListSectionMap.m */, + DD4F28A7CC1A638EF87DA9D5583C4CAA /* IGListStackedSectionControllerInternal.h */, + 4F1AA350148ECB86D3C7EAA99E50E71E /* IGListWorkingRangeHandler.h */, + 0103D32BB7A0A6165DCA97AE90B78FF3 /* IGListWorkingRangeHandler.mm */, + 5258E047034F850DB89896B38AC9EDB2 /* NSIndexSet+PrettyDescription.h */, + 380BD8B88A62F76284458B6E06908A47 /* NSIndexSet+PrettyDescription.m */, + 4F7D2957E3E38FF3DDE6BDE4FB7B5599 /* UICollectionView+IGListBatchUpdateData.h */, + 9E99717CDC2053D36C6789D1148FB800 /* UICollectionView+IGListBatchUpdateData.m */, + ); + path = Internal; + sourceTree = ""; + }; + 61F96534B3AFE724944526CC9F6F2EFE /* Products */ = { + isa = PBXGroup; + children = ( + 9D11DCAE06F644E6CCE659622CD8E616 /* IGListKit.framework */, + C7F454644FEBF5DB647AE1728D1FD067 /* Pods_IGListKitExamples.framework */, + ); + name = Products; + sourceTree = ""; + }; + 7DB346D0F39D3F0E887471402A8071AB = { + isa = PBXGroup; + children = ( + 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */, + 275D18F543186A905E745D345F66E093 /* Development Pods */, + 433CD3331B6C3787F473C941B61FC68F /* Frameworks */, + 61F96534B3AFE724944526CC9F6F2EFE /* Products */, + 51A62FAD141FCC64FABDAA958233E728 /* Targets Support Files */, + ); + sourceTree = ""; + }; + DAB5B0D31F1F31F2EA5708FF996EE588 /* Support Files */ = { + isa = PBXGroup; + children = ( + 561D1CAC9AE6D43A06CCEAFEC05B04DA /* IGListKit.modulemap */, + 891CE36E66A66905E25AD89B001B5648 /* IGListKit.xcconfig */, + 85FB0BDA4A7F33F3588C88434ECD2E94 /* IGListKit-dummy.m */, + 5B065D6991AF097908DF9824689FB9C4 /* IGListKit-prefix.pch */, + 82BBB8E0984E89E3CD3311E21D7F65D6 /* IGListKit-umbrella.h */, + BF8DCC2E99230AF598F30C8A54C525FB /* Info.plist */, + ); + name = "Support Files"; + path = "Example/Pods/Target Support Files/IGListKit"; + sourceTree = ""; + }; + F140F35915E040972E6D461E635BCD4C /* Source */ = { + isa = PBXGroup; + children = ( + 73259AA7FEA14577AEF2DB550C3020EB /* IGListAdapter.h */, + 6ACF94F01EB36325B7E6582A24CE67E1 /* IGListAdapter.m */, + FE4F9D5E24C8E166CBE1324FB4EFE901 /* IGListAdapterDataSource.h */, + 8C9AC4FA89437FC74B5044CB6E09F330 /* IGListAdapterDelegate.h */, + 60379E52F4C982E1C790B6519E71C7C7 /* IGListAdapterUpdater.h */, + 64E8ACC5C6782B94FD1B95596927A543 /* IGListAdapterUpdater.m */, + 923F1BE703C78DB09D0F325460A4A2EA /* IGListAdapterUpdaterDelegate.h */, + 5317DB3BAAEE0CC7B2D2B105A879060D /* IGListAssert.h */, + BE4F9093FE09B54A0DC32E8161904775 /* IGListBatchUpdateData.h */, + D0AEE94B91F9849EEF961CC1C265C985 /* IGListBatchUpdateData.mm */, + 17FC1649F3BE2A7A920FF0C7D50B7EC2 /* IGListCollectionContext.h */, + 1FE4AA45BC45A8EF0F9FB575B19EE801 /* IGListCollectionView.h */, + 4B5597025F567F3628A6FE6AB07D53F3 /* IGListCollectionView.m */, + B3A4D0DCC742ED05F9B0EAD98B483829 /* IGListDiff.h */, + 4B14381CF1C8B474F904BED10858A3B1 /* IGListDiff.mm */, + 54A169D9E10ABA4A657E466A81CF5E96 /* IGListDiffable.h */, + E2B529498914743C310A5BF137656A18 /* IGListDisplayDelegate.h */, + 8CFC4E4785DC5CD494D3BA69EE0EF1C5 /* IGListExperiments.h */, + DBA04B01FE70BC1FC9B2AF5A04816653 /* IGListIndexPathResult.h */, + DD1EB6F6A5F5CF2DA79688B3A01F95BC /* IGListIndexPathResult.m */, + 84752D2DDFC17301AD69B035A1ABEB98 /* IGListIndexSetResult.h */, + B281B0B7972DE5D26848178203A718AE /* IGListIndexSetResult.m */, + 1DD75371720A47691326032C3842EA7D /* IGListKit.h */, + 7E7FEF0C49C387ED472744AB5E63D5C5 /* IGListMacros.h */, + 4F331B7F935DA82F2BA8F9EC9CDAC989 /* IGListMoveIndex.h */, + D37ACDCF54F63073142D7196E56946A3 /* IGListMoveIndex.m */, + A25B427B6865597BD60B14637B4DF317 /* IGListMoveIndexPath.h */, + 7F401C4ED6FD8A5953382E94BFDC1B04 /* IGListMoveIndexPath.m */, + CDA049308F02171E1FE2541EFF16E527 /* IGListReloadDataUpdater.h */, + EBFD4BC90748BE8DE367B6DCA73B982A /* IGListReloadDataUpdater.m */, + A48E8E2621D1D301DF15DC1EA7F25CB7 /* IGListScrollDelegate.h */, + 50D66D11EB993B3B287DA0B8DEE153B7 /* IGListSectionController.h */, + F5AD8343AC8B755A3258D6E45FFD2D8A /* IGListSectionController.m */, + C0D34FDB422AF09AA6F7813D34DA3DCD /* IGListSectionType.h */, + A6A48D5B55D7631327C124F538C6E068 /* IGListSingleSectionController.h */, + 91007FC1F36EE50D4609909509B4BE04 /* IGListSingleSectionController.m */, + B9B2C760A6B8BC4E2F9B9BC221585A06 /* IGListStackedSectionController.h */, + 22F3AD163D43B95418E4F734FDD061F4 /* IGListStackedSectionController.m */, + D7D841899F69CAEFE9CC75F4C742EB7F /* IGListSupplementaryViewSource.h */, + 5074228D8F3A063BF5EE38BBCDC854D8 /* IGListUpdatingDelegate.h */, + D9F947E7710011A32E4FA3C988472760 /* IGListWorkingRangeDelegate.h */, + 516F4D39676D39DB68711DFCCF99CD08 /* NSObject+IGListDiffable.h */, + 720FAF43FD40FA27160DB53AD4C8E73D /* NSObject+IGListDiffable.m */, + 568515C4F4B02B048840EBD06A1CC65D /* Internal */, + ); + path = Source; + sourceTree = ""; + }; + F22E6673D29C1778B1984CF7D4D871E9 /* Pods-IGListKitExamples */ = { + isa = PBXGroup; + children = ( + ABF549428FEA18696E5358F599E11A24 /* Info.plist */, + DDF922EBFB237C1B42E8B07306D987E8 /* Pods-IGListKitExamples.modulemap */, + A7BBE69D34859663403DA26F14CC4DDB /* Pods-IGListKitExamples-acknowledgements.markdown */, + D803D538BF4074498E5ADB84A16E0395 /* Pods-IGListKitExamples-acknowledgements.plist */, + 2F5AE0385534EC7E6CA75003A7280492 /* Pods-IGListKitExamples-dummy.m */, + A851A4ACB8C0DE98BFFBC6FD4D1BACEE /* Pods-IGListKitExamples-frameworks.sh */, + 428C13C7C57B2D91F00D94ECF1FA71F8 /* Pods-IGListKitExamples-resources.sh */, + 52054AE893A0AE7FBFC54333D8ABD345 /* Pods-IGListKitExamples-umbrella.h */, + 1B2B14BA3AB402D9CC387EEB27A2F746 /* Pods-IGListKitExamples.debug.xcconfig */, + A198E265B2C6E673C7C9C5050F92D9F0 /* Pods-IGListKitExamples.release.xcconfig */, + ); + name = "Pods-IGListKitExamples"; + path = "Target Support Files/Pods-IGListKitExamples"; + sourceTree = ""; + }; + F302CD3B306AF1ED96B9AF31FCDBC26C /* iOS */ = { + isa = PBXGroup; + children = ( + A2A6FF208CEC4C2861F1E9D42D651453 /* Foundation.framework */, + E86668F20F252C440D1015B816BE59C5 /* UIKit.framework */, + ); + name = iOS; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 53EBE39810518423DDF4C14A0AB25552 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 35102F69615549F427FE2EC453149DAB /* IGListAdapter.h in Headers */, + 6B2EE19479243F984CE51F15ACCB1A33 /* IGListAdapterDataSource.h in Headers */, + 76B403D7072D98BED2AA6AA6C7827E39 /* IGListAdapterDelegate.h in Headers */, + 1EB6675BEF69B9E921CE4199DD7156D0 /* IGListAdapterInternal.h in Headers */, + 68A93F0E164CD1E9910C25698B5B0DC7 /* IGListAdapterProxy.h in Headers */, + BAB517802021813C296E2F2BA2C91FE2 /* IGListAdapterUpdater.h in Headers */, + A6091B575605FDBAD5E9E5645248FD66 /* IGListAdapterUpdaterDelegate.h in Headers */, + CCCD28F400F07B8C22AC80757491E13B /* IGListAdapterUpdaterInternal.h in Headers */, + 084B851CFFA9C0E2DF68D903E53E0EC8 /* IGListAssert.h in Headers */, + D46249EFD12F9C350961FEA1831DF20F /* IGListBatchUpdateData.h in Headers */, + 623527A4232D59FB38547902423B5AA2 /* IGListCollectionContext.h in Headers */, + 80AB64741918F4F2A7585C3C5FCBD98D /* IGListCollectionView.h in Headers */, + 503BDC35E6053B83D862316F3D8C935F /* IGListDiff.h in Headers */, + A01D6F5340CA1BD84E5DAB81B60A02A2 /* IGListDiffable.h in Headers */, + C4328DE47976156C06FB51E1FC1C79FA /* IGListDisplayDelegate.h in Headers */, + B01F1324E6269028CE385E78C26EF9D3 /* IGListDisplayHandler.h in Headers */, + B17B8BA5347B0717A112EB2BCE055293 /* IGListExperiments.h in Headers */, + 9EA7B904CC4B3E23C79B9B3909A51C2A /* IGListIndexPathResult.h in Headers */, + 0C27A4BED7FE0607F8E864854B0D4CB4 /* IGListIndexPathResultInternal.h in Headers */, + 931AB3AD8B295F26EDDB6FC6849EA247 /* IGListIndexSetResult.h in Headers */, + 1691094821EAD389A7A9C0BA79A970DB /* IGListIndexSetResultInternal.h in Headers */, + E01ABFB55E486E56D309DE5BAA801876 /* IGListKit-umbrella.h in Headers */, + F360C686D507AA39A221B314C92050B1 /* IGListKit.h in Headers */, + 444D6A0E181DF93311717CA41DB7B48A /* IGListMacros.h in Headers */, + 8F026EF7A1AB975A5411D2BDE5B4C081 /* IGListMoveIndex.h in Headers */, + 86B79ED8B51A8D5353E49A6C37DFE5CB /* IGListMoveIndexInternal.h in Headers */, + 6F06EF99F8A0B55F4E81D767EB7F4B36 /* IGListMoveIndexPath.h in Headers */, + 54EAA3B5C88905CD01B5FE2ADDF92FC6 /* IGListMoveIndexPathInternal.h in Headers */, + FD7CE90CC29F48232D88E3273B183752 /* IGListReloadDataUpdater.h in Headers */, + DBE42210054F8FDA69270F4EC611FD4C /* IGListScrollDelegate.h in Headers */, + 9F9C5D1351A6EB88531F759304A7A5D6 /* IGListSectionController.h in Headers */, + 6110D5294C734F5AF8FAF5AD4134789F /* IGListSectionControllerInternal.h in Headers */, + BF318B0B45ACAF2725C9AC468D256E3D /* IGListSectionMap.h in Headers */, + 6C43A407033E5A273E9E16EE7E334EB2 /* IGListSectionType.h in Headers */, + 2C58ED7F6EFFB13891EBAAF768E2C998 /* IGListSingleSectionController.h in Headers */, + 57FF7C6AED54D8A04C41DA36231BFC94 /* IGListStackedSectionController.h in Headers */, + B70560D9E6761A6E5666060E05DE071C /* IGListStackedSectionControllerInternal.h in Headers */, + 26750C29F741D0EF22EF396017265DC7 /* IGListSupplementaryViewSource.h in Headers */, + 1FD624CB427ECAEF7F1F9949E4B023AE /* IGListUpdatingDelegate.h in Headers */, + 83E3217B930A28021BC1DFC1DFD759A0 /* IGListWorkingRangeDelegate.h in Headers */, + 86BA72487680B4F3CFEB719F821D183A /* IGListWorkingRangeHandler.h in Headers */, + BFEEC2F47FC552E9E6EC80C984A8DE10 /* NSIndexSet+PrettyDescription.h in Headers */, + C7B337BF99A703262B38856C2A916613 /* NSObject+IGListDiffable.h in Headers */, + 9368BAE80054E3977919DCC164581318 /* UICollectionView+IGListBatchUpdateData.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B207714E2837D513687F9EE68AED37E4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + FF9FF8C3E7C149B40D25B5B2BB530C27 /* Pods-IGListKitExamples-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 26932F09684B706888A7BF05E69D387C /* IGListKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 535D892140575DCAAE9094BF475E7520 /* Build configuration list for PBXNativeTarget "IGListKit" */; + buildPhases = ( + E5B604A4F391F089A0370DD183AC19E3 /* Sources */, + FE924FF264CD5BDB6DFE9C2ACA63FCAC /* Frameworks */, + 53EBE39810518423DDF4C14A0AB25552 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IGListKit; + productName = IGListKit; + productReference = 9D11DCAE06F644E6CCE659622CD8E616 /* IGListKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 399B3DA0A0B4EE07A34985CAE12415EA /* Pods-IGListKitExamples */ = { + isa = PBXNativeTarget; + buildConfigurationList = D5ABECD9BBE4FF7324D45ECEE77ED0B8 /* Build configuration list for PBXNativeTarget "Pods-IGListKitExamples" */; + buildPhases = ( + C2EC7EB6182BF2E3B3BB74E91A191E66 /* Sources */, + AA75830E6081F3DABCD8A9D38D8D20E7 /* Frameworks */, + B207714E2837D513687F9EE68AED37E4 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + E4B48C859ACB78DD3BE68BBEBA62D2D0 /* PBXTargetDependency */, + ); + name = "Pods-IGListKitExamples"; + productName = "Pods-IGListKitExamples"; + productReference = C7F454644FEBF5DB647AE1728D1FD067 /* Pods_IGListKitExamples.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 = 61F96534B3AFE724944526CC9F6F2EFE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 26932F09684B706888A7BF05E69D387C /* IGListKit */, + 399B3DA0A0B4EE07A34985CAE12415EA /* Pods-IGListKitExamples */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + C2EC7EB6182BF2E3B3BB74E91A191E66 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B7FADDD58154CEA589481E450BD88E33 /* Pods-IGListKitExamples-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E5B604A4F391F089A0370DD183AC19E3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BC0126520E73770835746D3B1B613938 /* IGListAdapter.m in Sources */, + 171501700F6A914CD4C44E3666398BB5 /* IGListAdapterProxy.m in Sources */, + BF4804E0DD82EA483FF3C82F8DFCABBD /* IGListAdapterUpdater.m in Sources */, + 5455F7C71EB93A534D8A09AAFE8EF0FE /* IGListBatchUpdateData.mm in Sources */, + 4C6CC1B183686EB9EE4A7FF40CAFF697 /* IGListCollectionView.m in Sources */, + 25FDCBDCEA5575091B36BFE4B59D04CA /* IGListDiff.mm in Sources */, + 1CCB2FEACC2123958F5A0E39F70659B5 /* IGListDisplayHandler.m in Sources */, + 2912A571DF503B64C589EC9A44BA4B39 /* IGListIndexPathResult.m in Sources */, + EA5AEB7F2DADFF1BA74A38860AA60BA5 /* IGListIndexSetResult.m in Sources */, + C47B57F6C4A0B47A302C95C9FBD5BFDE /* IGListKit-dummy.m in Sources */, + 54CFA8A4442E7B0BB4C67FE811481E4C /* IGListMoveIndex.m in Sources */, + 32DA59CCC51F3C4A5239E87A45351EEE /* IGListMoveIndexPath.m in Sources */, + F5383C2327F50B07F0904BFE6EB496C8 /* IGListReloadDataUpdater.m in Sources */, + 135EAE77737104157F6CC0655BBA0ABA /* IGListSectionController.m in Sources */, + 026755843D7FB53E35679B703EFBBF6E /* IGListSectionMap.m in Sources */, + 213FF484E5786DD35F2A932774C57707 /* IGListSingleSectionController.m in Sources */, + 54E0CADE21353023E6F071C5FB164902 /* IGListStackedSectionController.m in Sources */, + 725B0E02B9D828F61443072352992F54 /* IGListWorkingRangeHandler.mm in Sources */, + F7023388DF86747C3752D4BCFFD9BE98 /* NSIndexSet+PrettyDescription.m in Sources */, + BA98207EA92AA990BE64F3C8970CBC9A /* NSObject+IGListDiffable.m in Sources */, + 0147435D65030952AE49DEC4E017B23D /* UICollectionView+IGListBatchUpdateData.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + E4B48C859ACB78DD3BE68BBEBA62D2D0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = IGListKit; + target = 26932F09684B706888A7BF05E69D387C /* IGListKit */; + targetProxy = 5A4D93DEC315EA1419504908E8F1310B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 044E174FC9DBD0528A6A23546D3945FB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 891CE36E66A66905E25AD89B001B5648 /* IGListKit.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/IGListKit/IGListKit-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/IGListKit/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/IGListKit/IGListKit.modulemap"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = IGListKit; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 419C2F52156B7DC129FD76F28DF37A3B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A198E265B2C6E673C7C9C5050F92D9F0 /* Pods-IGListKitExamples.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-IGListKitExamples/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.modulemap"; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = Pods_IGListKitExamples; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 47BEF9D903506B003EA5C2B249729489 /* 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 = 8.0; + ONLY_ACTIVE_ARCH = YES; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + 8A88E3170224FBEF18E2A77F2C52A3C1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1B2B14BA3AB402D9CC387EEB27A2F746 /* Pods-IGListKitExamples.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-IGListKitExamples/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.modulemap"; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = Pods_IGListKitExamples; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + AAF678CED40D3499169D10F63CA0719E /* 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 = 8.0; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + DCC9B305BBAAE5AF1B18323D97C89118 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 891CE36E66A66905E25AD89B001B5648 /* IGListKit.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/IGListKit/IGListKit-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/IGListKit/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/IGListKit/IGListKit.modulemap"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = IGListKit; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 47BEF9D903506B003EA5C2B249729489 /* Debug */, + AAF678CED40D3499169D10F63CA0719E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 535D892140575DCAAE9094BF475E7520 /* Build configuration list for PBXNativeTarget "IGListKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DCC9B305BBAAE5AF1B18323D97C89118 /* Debug */, + 044E174FC9DBD0528A6A23546D3945FB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D5ABECD9BBE4FF7324D45ECEE77ED0B8 /* Build configuration list for PBXNativeTarget "Pods-IGListKitExamples" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8A88E3170224FBEF18E2A77F2C52A3C1 /* Debug */, + 419C2F52156B7DC129FD76F28DF37A3B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; +} diff --git a/Example/Pods/Target Support Files/IGListKit/IGListKit-dummy.m b/Example/Pods/Target Support Files/IGListKit/IGListKit-dummy.m new file mode 100644 index 000000000..50cf88253 --- /dev/null +++ b/Example/Pods/Target Support Files/IGListKit/IGListKit-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_IGListKit : NSObject +@end +@implementation PodsDummy_IGListKit +@end diff --git a/Example/Pods/Target Support Files/IGListKit/IGListKit-prefix.pch b/Example/Pods/Target Support Files/IGListKit/IGListKit-prefix.pch new file mode 100644 index 000000000..aa992a4ad --- /dev/null +++ b/Example/Pods/Target Support Files/IGListKit/IGListKit-prefix.pch @@ -0,0 +1,4 @@ +#ifdef __OBJC__ +#import +#endif + diff --git a/Example/Pods/Target Support Files/IGListKit/IGListKit-umbrella.h b/Example/Pods/Target Support Files/IGListKit/IGListKit-umbrella.h new file mode 100644 index 000000000..eb071b2ab --- /dev/null +++ b/Example/Pods/Target Support Files/IGListKit/IGListKit-umbrella.h @@ -0,0 +1,35 @@ +#import + +#import "IGListAdapter.h" +#import "IGListAdapterDataSource.h" +#import "IGListAdapterDelegate.h" +#import "IGListAdapterUpdater.h" +#import "IGListAdapterUpdaterDelegate.h" +#import "IGListAssert.h" +#import "IGListBatchUpdateData.h" +#import "IGListCollectionContext.h" +#import "IGListCollectionView.h" +#import "IGListDiff.h" +#import "IGListDiffable.h" +#import "IGListDisplayDelegate.h" +#import "IGListExperiments.h" +#import "IGListIndexPathResult.h" +#import "IGListIndexSetResult.h" +#import "IGListKit.h" +#import "IGListMacros.h" +#import "IGListMoveIndex.h" +#import "IGListMoveIndexPath.h" +#import "IGListReloadDataUpdater.h" +#import "IGListScrollDelegate.h" +#import "IGListSectionController.h" +#import "IGListSectionType.h" +#import "IGListSingleSectionController.h" +#import "IGListStackedSectionController.h" +#import "IGListSupplementaryViewSource.h" +#import "IGListUpdatingDelegate.h" +#import "IGListWorkingRangeDelegate.h" +#import "NSObject+IGListDiffable.h" + +FOUNDATION_EXPORT double IGListKitVersionNumber; +FOUNDATION_EXPORT const unsigned char IGListKitVersionString[]; + diff --git a/Example/Pods/Target Support Files/IGListKit/IGListKit.modulemap b/Example/Pods/Target Support Files/IGListKit/IGListKit.modulemap new file mode 100644 index 000000000..76fc8f034 --- /dev/null +++ b/Example/Pods/Target Support Files/IGListKit/IGListKit.modulemap @@ -0,0 +1,6 @@ +framework module IGListKit { + umbrella header "IGListKit-umbrella.h" + + export * + module * { export * } +} diff --git a/Example/Pods/Target Support Files/IGListKit/IGListKit.xcconfig b/Example/Pods/Target Support Files/IGListKit/IGListKit.xcconfig new file mode 100644 index 000000000..d26e3749d --- /dev/null +++ b/Example/Pods/Target Support Files/IGListKit/IGListKit.xcconfig @@ -0,0 +1,11 @@ +CLANG_CXX_LANGUAGE_STANDARD = c++11 +CLANG_CXX_LIBRARY = libc++ +CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/IGListKit +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" +OTHER_LDFLAGS = -l"c++" -framework "UIKit" +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/Example/Pods/Target Support Files/IGListKit/Info.plist b/Example/Pods/Target Support Files/IGListKit/Info.plist new file mode 100644 index 000000000..2243fe6e2 --- /dev/null +++ b/Example/Pods/Target Support Files/IGListKit/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/Example/Pods/Target Support Files/Pods-IGListKitExamples/Info.plist b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Info.plist new file mode 100644 index 000000000..2243fe6e2 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/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/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.markdown new file mode 100644 index 000000000..92ddd3fac --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.markdown @@ -0,0 +1,37 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## IGListKit + +BSD License + +For IGListKit software + +Copyright (c) 2016, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.plist new file mode 100644 index 000000000..fd00e358a --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.plist @@ -0,0 +1,67 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD License + +For IGListKit software + +Copyright (c) 2016, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + IGListKit + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-dummy.m b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-dummy.m new file mode 100644 index 000000000..465dc7159 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_IGListKitExamples : NSObject +@end +@implementation PodsDummy_Pods_IGListKitExamples +@end diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-frameworks.sh b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-frameworks.sh new file mode 100755 index 000000000..b452f0e2a --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-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/IGListKit/IGListKit.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "$BUILT_PRODUCTS_DIR/IGListKit/IGListKit.framework" +fi diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-resources.sh b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-resources.sh new file mode 100755 index 000000000..0a1561528 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-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/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-umbrella.h b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-umbrella.h new file mode 100644 index 000000000..26ee99a48 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-umbrella.h @@ -0,0 +1,6 @@ +#import + + +FOUNDATION_EXPORT double Pods_IGListKitExamplesVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_IGListKitExamplesVersionString[]; + diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig new file mode 100644 index 000000000..39a8e31f8 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig @@ -0,0 +1,8 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" +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/IGListKit/IGListKit.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "IGListKit" +PODS_BUILD_DIR = $BUILD_DIR +PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.modulemap b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.modulemap new file mode 100644 index 000000000..b026bedb6 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.modulemap @@ -0,0 +1,6 @@ +framework module Pods_IGListKitExamples { + umbrella header "Pods-IGListKitExamples-umbrella.h" + + export * + module * { export * } +} diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig new file mode 100644 index 000000000..39a8e31f8 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig @@ -0,0 +1,8 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" +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/IGListKit/IGListKit.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "IGListKit" +PODS_BUILD_DIR = $BUILD_DIR +PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT}/Pods diff --git a/IGListKit.podspec b/IGListKit.podspec new file mode 100644 index 000000000..2816572f8 --- /dev/null +++ b/IGListKit.podspec @@ -0,0 +1,30 @@ +Pod::Spec.new do |s| + s.name = 'IGListKit' + s.version = '1.0' + s.summary = 'A data-driven UICollectionView framework.' + s.homepage = 'https://github.com/Instagram/IGListKit' + s.documentation_url = 'https://instagram.github.io/IGListKit' + s.description = 'A data-driven UICollectionView framework for building fast and flexible lists.' + + s.license = { :type => 'BSD' } + s.authors = 'Instagram' + s.social_media_url = 'https://twitter.com/fbOpenSource' + s.source = { + :git => 'https://github.com/Instagram/IGListKit.git', + :tag => s.version.to_s, + :branch => 'stable' + } + + s.source_files = 'Source/**/*.{h,m,mm}' + s.private_header_files = 'Source/Internal/*.h' + + s.requires_arc = true + s.platform = :ios, '8.0' + + s.frameworks = 'UIKit' + s.library = 'c++' + s.pod_target_xcconfig = { + 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++11', + 'CLANG_CXX_LIBRARY' => 'libc++' + } +end diff --git a/IGListKit.xcodeproj/project.pbxproj b/IGListKit.xcodeproj/project.pbxproj new file mode 100644 index 000000000..01d525644 --- /dev/null +++ b/IGListKit.xcodeproj/project.pbxproj @@ -0,0 +1,861 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5C81083F8E7AEF4B3EBE8871 /* Pods_IGListKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD40284889DE182FFC7F471E /* Pods_IGListKitTests.framework */; }; + 88144F071D870EDC007C7F66 /* IGListAdapterE2ETests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE21D870EDC007C7F66 /* IGListAdapterE2ETests.m */; }; + 88144F081D870EDC007C7F66 /* IGListAdapterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE31D870EDC007C7F66 /* IGListAdapterTests.m */; }; + 88144F091D870EDC007C7F66 /* IGListAdapterUpdaterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE41D870EDC007C7F66 /* IGListAdapterUpdaterTests.m */; }; + 88144F0A1D870EDC007C7F66 /* IGListBatchUpdateDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE51D870EDC007C7F66 /* IGListBatchUpdateDataTests.m */; }; + 88144F0B1D870EDC007C7F66 /* IGListDiffSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE61D870EDC007C7F66 /* IGListDiffSwiftTests.swift */; }; + 88144F0C1D870EDC007C7F66 /* IGListDiffTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE81D870EDC007C7F66 /* IGListDiffTests.m */; }; + 88144F0D1D870EDC007C7F66 /* IGListDisplayHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE91D870EDC007C7F66 /* IGListDisplayHandlerTests.m */; }; + 88144F0E1D870EDC007C7F66 /* IGListIndexResultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EEA1D870EDC007C7F66 /* IGListIndexResultTests.m */; }; + 88144F0F1D870EDC007C7F66 /* IGListObjectMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EEC1D870EDC007C7F66 /* IGListObjectMapTests.m */; }; + 88144F101D870EDC007C7F66 /* IGListSingleItemControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EED1D870EDC007C7F66 /* IGListSingleItemControllerTests.m */; }; + 88144F111D870EDC007C7F66 /* IGListStackItemControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EEE1D870EDC007C7F66 /* IGListStackItemControllerTests.m */; }; + 88144F121D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EEF1D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m */; }; + 88144F131D870EDC007C7F66 /* IGListTestAdapterDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EF21D870EDC007C7F66 /* IGListTestAdapterDataSource.m */; }; + 88144F141D870EDC007C7F66 /* IGListTestOffsettingLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EF41D870EDC007C7F66 /* IGListTestOffsettingLayout.m */; }; + 88144F151D870EDC007C7F66 /* IGListTestSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EF61D870EDC007C7F66 /* IGListTestSection.m */; }; + 88144F161D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EF81D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.m */; }; + 88144F171D870EDC007C7F66 /* IGTestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EFA1D870EDC007C7F66 /* IGTestCell.m */; }; + 88144F181D870EDC007C7F66 /* IGTestDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EFC1D870EDC007C7F66 /* IGTestDelegateController.m */; }; + 88144F191D870EDC007C7F66 /* IGTestDelegateDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EFE1D870EDC007C7F66 /* IGTestDelegateDataSource.m */; }; + 88144F1A1D870EDC007C7F66 /* IGTestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F001D870EDC007C7F66 /* IGTestObject.m */; }; + 88144F1B1D870EDC007C7F66 /* IGTestSingleItemDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F021D870EDC007C7F66 /* IGTestSingleItemDataSource.m */; }; + 88144F1C1D870EDC007C7F66 /* IGTestStackedDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F041D870EDC007C7F66 /* IGTestStackedDataSource.m */; }; + 88144F1D1D870EDC007C7F66 /* IGTestSupplementarySource.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F061D870EDC007C7F66 /* IGTestSupplementarySource.m */; }; + 88144F5C1D870F3E007C7F66 /* IGListAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F1E1D870F3E007C7F66 /* IGListAdapter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F5D1D870F3E007C7F66 /* IGListAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F1F1D870F3E007C7F66 /* IGListAdapter.m */; }; + 88144F5E1D870F3E007C7F66 /* IGListAdapterDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F201D870F3E007C7F66 /* IGListAdapterDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F5F1D870F3E007C7F66 /* IGListAdapterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F211D870F3E007C7F66 /* IGListAdapterDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F601D870F3E007C7F66 /* IGListAdapterUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F221D870F3E007C7F66 /* IGListAdapterUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F611D870F3E007C7F66 /* IGListAdapterUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F231D870F3E007C7F66 /* IGListAdapterUpdater.m */; }; + 88144F621D870F3E007C7F66 /* IGListAdapterUpdaterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F241D870F3E007C7F66 /* IGListAdapterUpdaterDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F631D870F3E007C7F66 /* IGListAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F251D870F3E007C7F66 /* IGListAssert.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F641D870F3E007C7F66 /* IGListBatchUpdateData.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F261D870F3E007C7F66 /* IGListBatchUpdateData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F651D870F3E007C7F66 /* IGListBatchUpdateData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88144F271D870F3E007C7F66 /* IGListBatchUpdateData.mm */; }; + 88144F661D870F3E007C7F66 /* IGListCollectionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F281D870F3E007C7F66 /* IGListCollectionContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F671D870F3E007C7F66 /* IGListCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F291D870F3E007C7F66 /* IGListCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F681D870F3E007C7F66 /* IGListCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F2A1D870F3E007C7F66 /* IGListCollectionView.m */; }; + 88144F691D870F3E007C7F66 /* IGListDiff.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F2B1D870F3E007C7F66 /* IGListDiff.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F6A1D870F3E007C7F66 /* IGListDiff.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88144F2C1D870F3E007C7F66 /* IGListDiff.mm */; }; + 88144F6B1D870F3E007C7F66 /* IGListDiffable.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F2D1D870F3E007C7F66 /* IGListDiffable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F6C1D870F3E007C7F66 /* IGListDisplayDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F2E1D870F3E007C7F66 /* IGListDisplayDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F6D1D870F3E007C7F66 /* IGListExperiments.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F2F1D870F3E007C7F66 /* IGListExperiments.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F6E1D870F3E007C7F66 /* IGListIndexPathResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F301D870F3E007C7F66 /* IGListIndexPathResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F6F1D870F3E007C7F66 /* IGListIndexPathResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F311D870F3E007C7F66 /* IGListIndexPathResult.m */; }; + 88144F701D870F3E007C7F66 /* IGListIndexSetResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F321D870F3E007C7F66 /* IGListIndexSetResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F711D870F3E007C7F66 /* IGListIndexSetResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F331D870F3E007C7F66 /* IGListIndexSetResult.m */; }; + 88144F721D870F3E007C7F66 /* IGListItemController.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F341D870F3E007C7F66 /* IGListItemController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F731D870F3E007C7F66 /* IGListItemController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F351D870F3E007C7F66 /* IGListItemController.m */; }; + 88144F741D870F3E007C7F66 /* IGListItemType.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F361D870F3E007C7F66 /* IGListItemType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F751D870F3E007C7F66 /* IGListMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F371D870F3E007C7F66 /* IGListMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F761D870F3E007C7F66 /* IGListMoveIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F381D870F3E007C7F66 /* IGListMoveIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F771D870F3E007C7F66 /* IGListMoveIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F391D870F3E007C7F66 /* IGListMoveIndex.m */; }; + 88144F781D870F3E007C7F66 /* IGListMoveIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F3A1D870F3E007C7F66 /* IGListMoveIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F791D870F3E007C7F66 /* IGListMoveIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F3B1D870F3E007C7F66 /* IGListMoveIndexPath.m */; }; + 88144F7A1D870F3E007C7F66 /* IGListReloadDataUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F3C1D870F3E007C7F66 /* IGListReloadDataUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F7B1D870F3E007C7F66 /* IGListReloadDataUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F3D1D870F3E007C7F66 /* IGListReloadDataUpdater.m */; }; + 88144F7C1D870F3E007C7F66 /* IGListSingleItemController.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F3E1D870F3E007C7F66 /* IGListSingleItemController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F7D1D870F3E007C7F66 /* IGListSingleItemController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F3F1D870F3E007C7F66 /* IGListSingleItemController.m */; }; + 88144F7E1D870F3E007C7F66 /* IGListStackedItemController.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F401D870F3E007C7F66 /* IGListStackedItemController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F7F1D870F3E007C7F66 /* IGListStackedItemController.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F411D870F3E007C7F66 /* IGListStackedItemController.m */; }; + 88144F801D870F3E007C7F66 /* IGListSupplementaryViewSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F421D870F3E007C7F66 /* IGListSupplementaryViewSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F811D870F3E007C7F66 /* IGListUpdatingDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F431D870F3E007C7F66 /* IGListUpdatingDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F821D870F3E007C7F66 /* IGListWorkingRangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F441D870F3E007C7F66 /* IGListWorkingRangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F831D870F3E007C7F66 /* IGListAdapterInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F461D870F3E007C7F66 /* IGListAdapterInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F841D870F3E007C7F66 /* IGListAdapterProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F471D870F3E007C7F66 /* IGListAdapterProxy.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F851D870F3E007C7F66 /* IGListAdapterProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F481D870F3E007C7F66 /* IGListAdapterProxy.m */; }; + 88144F861D870F3E007C7F66 /* IGListAdapterUpdaterInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F491D870F3E007C7F66 /* IGListAdapterUpdaterInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F871D870F3E007C7F66 /* IGListDisplayHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F4A1D870F3E007C7F66 /* IGListDisplayHandler.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F881D870F3E007C7F66 /* IGListDisplayHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F4B1D870F3E007C7F66 /* IGListDisplayHandler.m */; }; + 88144F891D870F3E007C7F66 /* IGListIndexPathResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F4C1D870F3E007C7F66 /* IGListIndexPathResultInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F8A1D870F3E007C7F66 /* IGListIndexSetResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F4D1D870F3E007C7F66 /* IGListIndexSetResultInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F8B1D870F3E007C7F66 /* IGListItemControllerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F4E1D870F3E007C7F66 /* IGListItemControllerInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F8C1D870F3E007C7F66 /* IGListItemMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F4F1D870F3E007C7F66 /* IGListItemMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F8D1D870F3E007C7F66 /* IGListItemMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F501D870F3E007C7F66 /* IGListItemMap.m */; }; + 88144F8E1D870F3E007C7F66 /* IGListMoveIndexInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F511D870F3E007C7F66 /* IGListMoveIndexInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F8F1D870F3E007C7F66 /* IGListMoveIndexPathInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F521D870F3E007C7F66 /* IGListMoveIndexPathInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F901D870F3E007C7F66 /* IGListStackedItemControllerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F531D870F3E007C7F66 /* IGListStackedItemControllerInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F911D870F3E007C7F66 /* IGListWorkingRangeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F541D870F3E007C7F66 /* IGListWorkingRangeHandler.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F921D870F3E007C7F66 /* IGListWorkingRangeHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88144F551D870F3E007C7F66 /* IGListWorkingRangeHandler.mm */; }; + 88144F931D870F3E007C7F66 /* NSIndexSet+PrettyDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F561D870F3E007C7F66 /* NSIndexSet+PrettyDescription.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F941D870F3E007C7F66 /* NSIndexSet+PrettyDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F571D870F3E007C7F66 /* NSIndexSet+PrettyDescription.m */; }; + 88144F951D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F581D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 88144F961D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F591D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.m */; }; + 88144F971D870F3E007C7F66 /* NSObject+IGListDiffable.h in Headers */ = {isa = PBXBuildFile; fileRef = 88144F5A1D870F3E007C7F66 /* NSObject+IGListDiffable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88144F981D870F3E007C7F66 /* NSObject+IGListDiffable.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144F5B1D870F3E007C7F66 /* NSObject+IGListDiffable.m */; }; + 887D0B401D870D7F009E01F7 /* IGListKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 887D0B361D870D7E009E01F7 /* IGListKit.framework */; }; + 887D0B531D870DFE009E01F7 /* IGListKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 887D0B511D870DFE009E01F7 /* IGListKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 887D0B411D870D7F009E01F7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 887D0B2D1D870D7E009E01F7 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 887D0B351D870D7E009E01F7; + remoteInfo = IGListKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 6BCA3FF59943AD1DAC2077E3 /* Pods-IGListKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.release.xcconfig"; sourceTree = ""; }; + 88144EE21D870EDC007C7F66 /* IGListAdapterE2ETests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterE2ETests.m; sourceTree = ""; }; + 88144EE31D870EDC007C7F66 /* IGListAdapterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterTests.m; sourceTree = ""; }; + 88144EE41D870EDC007C7F66 /* IGListAdapterUpdaterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterUpdaterTests.m; sourceTree = ""; }; + 88144EE51D870EDC007C7F66 /* IGListBatchUpdateDataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListBatchUpdateDataTests.m; sourceTree = ""; }; + 88144EE61D870EDC007C7F66 /* IGListDiffSwiftTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IGListDiffSwiftTests.swift; sourceTree = ""; }; + 88144EE81D870EDC007C7F66 /* IGListDiffTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListDiffTests.m; sourceTree = ""; }; + 88144EE91D870EDC007C7F66 /* IGListDisplayHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListDisplayHandlerTests.m; sourceTree = ""; }; + 88144EEA1D870EDC007C7F66 /* IGListIndexResultTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListIndexResultTests.m; sourceTree = ""; }; + 88144EEB1D870EDC007C7F66 /* IGListKitTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListKitTests-Bridging-Header.h"; sourceTree = ""; }; + 88144EEC1D870EDC007C7F66 /* IGListObjectMapTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListObjectMapTests.m; sourceTree = ""; }; + 88144EED1D870EDC007C7F66 /* IGListSingleItemControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListSingleItemControllerTests.m; sourceTree = ""; }; + 88144EEE1D870EDC007C7F66 /* IGListStackItemControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListStackItemControllerTests.m; sourceTree = ""; }; + 88144EEF1D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListWorkingRangeHandlerTests.m; sourceTree = ""; }; + 88144EF11D870EDC007C7F66 /* IGListTestAdapterDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListTestAdapterDataSource.h; sourceTree = ""; }; + 88144EF21D870EDC007C7F66 /* IGListTestAdapterDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListTestAdapterDataSource.m; sourceTree = ""; }; + 88144EF31D870EDC007C7F66 /* IGListTestOffsettingLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListTestOffsettingLayout.h; sourceTree = ""; }; + 88144EF41D870EDC007C7F66 /* IGListTestOffsettingLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListTestOffsettingLayout.m; sourceTree = ""; }; + 88144EF51D870EDC007C7F66 /* IGListTestSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListTestSection.h; sourceTree = ""; }; + 88144EF61D870EDC007C7F66 /* IGListTestSection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListTestSection.m; sourceTree = ""; }; + 88144EF71D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListTestUICollectionViewDataSource.h; sourceTree = ""; }; + 88144EF81D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListTestUICollectionViewDataSource.m; sourceTree = ""; }; + 88144EF91D870EDC007C7F66 /* IGTestCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestCell.h; sourceTree = ""; }; + 88144EFA1D870EDC007C7F66 /* IGTestCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestCell.m; sourceTree = ""; }; + 88144EFB1D870EDC007C7F66 /* IGTestDelegateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestDelegateController.h; sourceTree = ""; }; + 88144EFC1D870EDC007C7F66 /* IGTestDelegateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestDelegateController.m; sourceTree = ""; }; + 88144EFD1D870EDC007C7F66 /* IGTestDelegateDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestDelegateDataSource.h; sourceTree = ""; }; + 88144EFE1D870EDC007C7F66 /* IGTestDelegateDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestDelegateDataSource.m; sourceTree = ""; }; + 88144EFF1D870EDC007C7F66 /* IGTestObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestObject.h; sourceTree = ""; }; + 88144F001D870EDC007C7F66 /* IGTestObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestObject.m; sourceTree = ""; }; + 88144F011D870EDC007C7F66 /* IGTestSingleItemDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestSingleItemDataSource.h; sourceTree = ""; }; + 88144F021D870EDC007C7F66 /* IGTestSingleItemDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestSingleItemDataSource.m; sourceTree = ""; }; + 88144F031D870EDC007C7F66 /* IGTestStackedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestStackedDataSource.h; sourceTree = ""; }; + 88144F041D870EDC007C7F66 /* IGTestStackedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestStackedDataSource.m; sourceTree = ""; }; + 88144F051D870EDC007C7F66 /* IGTestSupplementarySource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestSupplementarySource.h; sourceTree = ""; }; + 88144F061D870EDC007C7F66 /* IGTestSupplementarySource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestSupplementarySource.m; sourceTree = ""; }; + 88144F1E1D870F3E007C7F66 /* IGListAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapter.h; sourceTree = ""; }; + 88144F1F1D870F3E007C7F66 /* IGListAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapter.m; sourceTree = ""; }; + 88144F201D870F3E007C7F66 /* IGListAdapterDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapterDataSource.h; sourceTree = ""; }; + 88144F211D870F3E007C7F66 /* IGListAdapterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapterDelegate.h; sourceTree = ""; }; + 88144F221D870F3E007C7F66 /* IGListAdapterUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapterUpdater.h; sourceTree = ""; }; + 88144F231D870F3E007C7F66 /* IGListAdapterUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterUpdater.m; sourceTree = ""; }; + 88144F241D870F3E007C7F66 /* IGListAdapterUpdaterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapterUpdaterDelegate.h; sourceTree = ""; }; + 88144F251D870F3E007C7F66 /* IGListAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAssert.h; sourceTree = ""; }; + 88144F261D870F3E007C7F66 /* IGListBatchUpdateData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListBatchUpdateData.h; sourceTree = ""; }; + 88144F271D870F3E007C7F66 /* IGListBatchUpdateData.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IGListBatchUpdateData.mm; sourceTree = ""; }; + 88144F281D870F3E007C7F66 /* IGListCollectionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListCollectionContext.h; sourceTree = ""; }; + 88144F291D870F3E007C7F66 /* IGListCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListCollectionView.h; sourceTree = ""; }; + 88144F2A1D870F3E007C7F66 /* IGListCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListCollectionView.m; sourceTree = ""; }; + 88144F2B1D870F3E007C7F66 /* IGListDiff.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListDiff.h; sourceTree = ""; }; + 88144F2C1D870F3E007C7F66 /* IGListDiff.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IGListDiff.mm; sourceTree = ""; }; + 88144F2D1D870F3E007C7F66 /* IGListDiffable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListDiffable.h; sourceTree = ""; }; + 88144F2E1D870F3E007C7F66 /* IGListDisplayDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListDisplayDelegate.h; sourceTree = ""; }; + 88144F2F1D870F3E007C7F66 /* IGListExperiments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListExperiments.h; sourceTree = ""; }; + 88144F301D870F3E007C7F66 /* IGListIndexPathResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListIndexPathResult.h; sourceTree = ""; }; + 88144F311D870F3E007C7F66 /* IGListIndexPathResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListIndexPathResult.m; sourceTree = ""; }; + 88144F321D870F3E007C7F66 /* IGListIndexSetResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListIndexSetResult.h; sourceTree = ""; }; + 88144F331D870F3E007C7F66 /* IGListIndexSetResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListIndexSetResult.m; sourceTree = ""; }; + 88144F341D870F3E007C7F66 /* IGListItemController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListItemController.h; sourceTree = ""; }; + 88144F351D870F3E007C7F66 /* IGListItemController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListItemController.m; sourceTree = ""; }; + 88144F361D870F3E007C7F66 /* IGListItemType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListItemType.h; sourceTree = ""; }; + 88144F371D870F3E007C7F66 /* IGListMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListMacros.h; sourceTree = ""; }; + 88144F381D870F3E007C7F66 /* IGListMoveIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListMoveIndex.h; sourceTree = ""; }; + 88144F391D870F3E007C7F66 /* IGListMoveIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListMoveIndex.m; sourceTree = ""; }; + 88144F3A1D870F3E007C7F66 /* IGListMoveIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListMoveIndexPath.h; sourceTree = ""; }; + 88144F3B1D870F3E007C7F66 /* IGListMoveIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListMoveIndexPath.m; sourceTree = ""; }; + 88144F3C1D870F3E007C7F66 /* IGListReloadDataUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListReloadDataUpdater.h; sourceTree = ""; }; + 88144F3D1D870F3E007C7F66 /* IGListReloadDataUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListReloadDataUpdater.m; sourceTree = ""; }; + 88144F3E1D870F3E007C7F66 /* IGListSingleItemController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListSingleItemController.h; sourceTree = ""; }; + 88144F3F1D870F3E007C7F66 /* IGListSingleItemController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListSingleItemController.m; sourceTree = ""; }; + 88144F401D870F3E007C7F66 /* IGListStackedItemController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListStackedItemController.h; sourceTree = ""; }; + 88144F411D870F3E007C7F66 /* IGListStackedItemController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListStackedItemController.m; sourceTree = ""; }; + 88144F421D870F3E007C7F66 /* IGListSupplementaryViewSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListSupplementaryViewSource.h; sourceTree = ""; }; + 88144F431D870F3E007C7F66 /* IGListUpdatingDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListUpdatingDelegate.h; sourceTree = ""; }; + 88144F441D870F3E007C7F66 /* IGListWorkingRangeDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListWorkingRangeDelegate.h; sourceTree = ""; }; + 88144F461D870F3E007C7F66 /* IGListAdapterInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapterInternal.h; sourceTree = ""; }; + 88144F471D870F3E007C7F66 /* IGListAdapterProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapterProxy.h; sourceTree = ""; }; + 88144F481D870F3E007C7F66 /* IGListAdapterProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterProxy.m; sourceTree = ""; }; + 88144F491D870F3E007C7F66 /* IGListAdapterUpdaterInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapterUpdaterInternal.h; sourceTree = ""; }; + 88144F4A1D870F3E007C7F66 /* IGListDisplayHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListDisplayHandler.h; sourceTree = ""; }; + 88144F4B1D870F3E007C7F66 /* IGListDisplayHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListDisplayHandler.m; sourceTree = ""; }; + 88144F4C1D870F3E007C7F66 /* IGListIndexPathResultInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListIndexPathResultInternal.h; sourceTree = ""; }; + 88144F4D1D870F3E007C7F66 /* IGListIndexSetResultInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListIndexSetResultInternal.h; sourceTree = ""; }; + 88144F4E1D870F3E007C7F66 /* IGListItemControllerInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListItemControllerInternal.h; sourceTree = ""; }; + 88144F4F1D870F3E007C7F66 /* IGListItemMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListItemMap.h; sourceTree = ""; }; + 88144F501D870F3E007C7F66 /* IGListItemMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListItemMap.m; sourceTree = ""; }; + 88144F511D870F3E007C7F66 /* IGListMoveIndexInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListMoveIndexInternal.h; sourceTree = ""; }; + 88144F521D870F3E007C7F66 /* IGListMoveIndexPathInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListMoveIndexPathInternal.h; sourceTree = ""; }; + 88144F531D870F3E007C7F66 /* IGListStackedItemControllerInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListStackedItemControllerInternal.h; sourceTree = ""; }; + 88144F541D870F3E007C7F66 /* IGListWorkingRangeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListWorkingRangeHandler.h; sourceTree = ""; }; + 88144F551D870F3E007C7F66 /* IGListWorkingRangeHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IGListWorkingRangeHandler.mm; sourceTree = ""; }; + 88144F561D870F3E007C7F66 /* NSIndexSet+PrettyDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+PrettyDescription.h"; sourceTree = ""; }; + 88144F571D870F3E007C7F66 /* NSIndexSet+PrettyDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexSet+PrettyDescription.m"; sourceTree = ""; }; + 88144F581D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+IGListBatchUpdateData.h"; sourceTree = ""; }; + 88144F591D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+IGListBatchUpdateData.m"; sourceTree = ""; }; + 88144F5A1D870F3E007C7F66 /* NSObject+IGListDiffable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+IGListDiffable.h"; sourceTree = ""; }; + 88144F5B1D870F3E007C7F66 /* NSObject+IGListDiffable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+IGListDiffable.m"; sourceTree = ""; }; + 887D0B361D870D7E009E01F7 /* IGListKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IGListKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 887D0B3F1D870D7F009E01F7 /* IGListKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IGListKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 887D0B511D870DFE009E01F7 /* IGListKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListKit.h; sourceTree = ""; }; + 887D0B521D870DFE009E01F7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 887D0B571D870E1E009E01F7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C4A16046BECFD57B7EB75259 /* Pods-IGListKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.debug.xcconfig"; sourceTree = ""; }; + FD40284889DE182FFC7F471E /* Pods_IGListKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IGListKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 887D0B321D870D7E009E01F7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 887D0B3C1D870D7F009E01F7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 887D0B401D870D7F009E01F7 /* IGListKit.framework in Frameworks */, + 5C81083F8E7AEF4B3EBE8871 /* Pods_IGListKitTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2F54C5CD9AC0E162B7763498 /* Pods */ = { + isa = PBXGroup; + children = ( + C4A16046BECFD57B7EB75259 /* Pods-IGListKitTests.debug.xcconfig */, + 6BCA3FF59943AD1DAC2077E3 /* Pods-IGListKitTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 88144EF01D870EDC007C7F66 /* Objects */ = { + isa = PBXGroup; + children = ( + 88144EF11D870EDC007C7F66 /* IGListTestAdapterDataSource.h */, + 88144EF21D870EDC007C7F66 /* IGListTestAdapterDataSource.m */, + 88144EF31D870EDC007C7F66 /* IGListTestOffsettingLayout.h */, + 88144EF41D870EDC007C7F66 /* IGListTestOffsettingLayout.m */, + 88144EF51D870EDC007C7F66 /* IGListTestSection.h */, + 88144EF61D870EDC007C7F66 /* IGListTestSection.m */, + 88144EF71D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.h */, + 88144EF81D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.m */, + 88144EF91D870EDC007C7F66 /* IGTestCell.h */, + 88144EFA1D870EDC007C7F66 /* IGTestCell.m */, + 88144EFB1D870EDC007C7F66 /* IGTestDelegateController.h */, + 88144EFC1D870EDC007C7F66 /* IGTestDelegateController.m */, + 88144EFD1D870EDC007C7F66 /* IGTestDelegateDataSource.h */, + 88144EFE1D870EDC007C7F66 /* IGTestDelegateDataSource.m */, + 88144EFF1D870EDC007C7F66 /* IGTestObject.h */, + 88144F001D870EDC007C7F66 /* IGTestObject.m */, + 88144F011D870EDC007C7F66 /* IGTestSingleItemDataSource.h */, + 88144F021D870EDC007C7F66 /* IGTestSingleItemDataSource.m */, + 88144F031D870EDC007C7F66 /* IGTestStackedDataSource.h */, + 88144F041D870EDC007C7F66 /* IGTestStackedDataSource.m */, + 88144F051D870EDC007C7F66 /* IGTestSupplementarySource.h */, + 88144F061D870EDC007C7F66 /* IGTestSupplementarySource.m */, + ); + path = Objects; + sourceTree = ""; + }; + 88144F451D870F3E007C7F66 /* Internal */ = { + isa = PBXGroup; + children = ( + 88144F461D870F3E007C7F66 /* IGListAdapterInternal.h */, + 88144F471D870F3E007C7F66 /* IGListAdapterProxy.h */, + 88144F481D870F3E007C7F66 /* IGListAdapterProxy.m */, + 88144F491D870F3E007C7F66 /* IGListAdapterUpdaterInternal.h */, + 88144F4A1D870F3E007C7F66 /* IGListDisplayHandler.h */, + 88144F4B1D870F3E007C7F66 /* IGListDisplayHandler.m */, + 88144F4C1D870F3E007C7F66 /* IGListIndexPathResultInternal.h */, + 88144F4D1D870F3E007C7F66 /* IGListIndexSetResultInternal.h */, + 88144F4E1D870F3E007C7F66 /* IGListItemControllerInternal.h */, + 88144F4F1D870F3E007C7F66 /* IGListItemMap.h */, + 88144F501D870F3E007C7F66 /* IGListItemMap.m */, + 88144F511D870F3E007C7F66 /* IGListMoveIndexInternal.h */, + 88144F521D870F3E007C7F66 /* IGListMoveIndexPathInternal.h */, + 88144F531D870F3E007C7F66 /* IGListStackedItemControllerInternal.h */, + 88144F541D870F3E007C7F66 /* IGListWorkingRangeHandler.h */, + 88144F551D870F3E007C7F66 /* IGListWorkingRangeHandler.mm */, + 88144F561D870F3E007C7F66 /* NSIndexSet+PrettyDescription.h */, + 88144F571D870F3E007C7F66 /* NSIndexSet+PrettyDescription.m */, + 88144F581D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.h */, + 88144F591D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.m */, + ); + path = Internal; + sourceTree = ""; + }; + 887D0B2C1D870D7E009E01F7 = { + isa = PBXGroup; + children = ( + 887D0B501D870DFE009E01F7 /* Source */, + 887D0B551D870E1E009E01F7 /* Tests */, + 887D0B371D870D7E009E01F7 /* Products */, + 2F54C5CD9AC0E162B7763498 /* Pods */, + E50B2A397FADE75CAA9C57A6 /* Frameworks */, + ); + sourceTree = ""; + }; + 887D0B371D870D7E009E01F7 /* Products */ = { + isa = PBXGroup; + children = ( + 887D0B361D870D7E009E01F7 /* IGListKit.framework */, + 887D0B3F1D870D7F009E01F7 /* IGListKitTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 887D0B501D870DFE009E01F7 /* Source */ = { + isa = PBXGroup; + children = ( + 88144F1E1D870F3E007C7F66 /* IGListAdapter.h */, + 88144F1F1D870F3E007C7F66 /* IGListAdapter.m */, + 88144F201D870F3E007C7F66 /* IGListAdapterDataSource.h */, + 88144F211D870F3E007C7F66 /* IGListAdapterDelegate.h */, + 88144F221D870F3E007C7F66 /* IGListAdapterUpdater.h */, + 88144F231D870F3E007C7F66 /* IGListAdapterUpdater.m */, + 88144F241D870F3E007C7F66 /* IGListAdapterUpdaterDelegate.h */, + 88144F251D870F3E007C7F66 /* IGListAssert.h */, + 88144F261D870F3E007C7F66 /* IGListBatchUpdateData.h */, + 88144F271D870F3E007C7F66 /* IGListBatchUpdateData.mm */, + 88144F281D870F3E007C7F66 /* IGListCollectionContext.h */, + 88144F291D870F3E007C7F66 /* IGListCollectionView.h */, + 88144F2A1D870F3E007C7F66 /* IGListCollectionView.m */, + 88144F2B1D870F3E007C7F66 /* IGListDiff.h */, + 88144F2C1D870F3E007C7F66 /* IGListDiff.mm */, + 88144F2D1D870F3E007C7F66 /* IGListDiffable.h */, + 88144F2E1D870F3E007C7F66 /* IGListDisplayDelegate.h */, + 88144F2F1D870F3E007C7F66 /* IGListExperiments.h */, + 88144F301D870F3E007C7F66 /* IGListIndexPathResult.h */, + 88144F311D870F3E007C7F66 /* IGListIndexPathResult.m */, + 88144F321D870F3E007C7F66 /* IGListIndexSetResult.h */, + 88144F331D870F3E007C7F66 /* IGListIndexSetResult.m */, + 88144F341D870F3E007C7F66 /* IGListItemController.h */, + 88144F351D870F3E007C7F66 /* IGListItemController.m */, + 88144F361D870F3E007C7F66 /* IGListItemType.h */, + 887D0B511D870DFE009E01F7 /* IGListKit.h */, + 88144F371D870F3E007C7F66 /* IGListMacros.h */, + 88144F381D870F3E007C7F66 /* IGListMoveIndex.h */, + 88144F391D870F3E007C7F66 /* IGListMoveIndex.m */, + 88144F3A1D870F3E007C7F66 /* IGListMoveIndexPath.h */, + 88144F3B1D870F3E007C7F66 /* IGListMoveIndexPath.m */, + 88144F3C1D870F3E007C7F66 /* IGListReloadDataUpdater.h */, + 88144F3D1D870F3E007C7F66 /* IGListReloadDataUpdater.m */, + 88144F3E1D870F3E007C7F66 /* IGListSingleItemController.h */, + 88144F3F1D870F3E007C7F66 /* IGListSingleItemController.m */, + 88144F401D870F3E007C7F66 /* IGListStackedItemController.h */, + 88144F411D870F3E007C7F66 /* IGListStackedItemController.m */, + 88144F421D870F3E007C7F66 /* IGListSupplementaryViewSource.h */, + 88144F431D870F3E007C7F66 /* IGListUpdatingDelegate.h */, + 88144F441D870F3E007C7F66 /* IGListWorkingRangeDelegate.h */, + 887D0B521D870DFE009E01F7 /* Info.plist */, + 88144F451D870F3E007C7F66 /* Internal */, + 88144F5A1D870F3E007C7F66 /* NSObject+IGListDiffable.h */, + 88144F5B1D870F3E007C7F66 /* NSObject+IGListDiffable.m */, + ); + path = Source; + sourceTree = ""; + }; + 887D0B551D870E1E009E01F7 /* Tests */ = { + isa = PBXGroup; + children = ( + 88144EE21D870EDC007C7F66 /* IGListAdapterE2ETests.m */, + 88144EE31D870EDC007C7F66 /* IGListAdapterTests.m */, + 88144EE41D870EDC007C7F66 /* IGListAdapterUpdaterTests.m */, + 88144EE51D870EDC007C7F66 /* IGListBatchUpdateDataTests.m */, + 88144EE61D870EDC007C7F66 /* IGListDiffSwiftTests.swift */, + 88144EE81D870EDC007C7F66 /* IGListDiffTests.m */, + 88144EE91D870EDC007C7F66 /* IGListDisplayHandlerTests.m */, + 88144EEA1D870EDC007C7F66 /* IGListIndexResultTests.m */, + 88144EEB1D870EDC007C7F66 /* IGListKitTests-Bridging-Header.h */, + 88144EEC1D870EDC007C7F66 /* IGListObjectMapTests.m */, + 88144EED1D870EDC007C7F66 /* IGListSingleItemControllerTests.m */, + 88144EEE1D870EDC007C7F66 /* IGListStackItemControllerTests.m */, + 88144EEF1D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m */, + 887D0B571D870E1E009E01F7 /* Info.plist */, + 88144EF01D870EDC007C7F66 /* Objects */, + ); + path = Tests; + sourceTree = ""; + }; + E50B2A397FADE75CAA9C57A6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + FD40284889DE182FFC7F471E /* Pods_IGListKitTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 887D0B331D870D7E009E01F7 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 88144F7C1D870F3E007C7F66 /* IGListSingleItemController.h in Headers */, + 88144F641D870F3E007C7F66 /* IGListBatchUpdateData.h in Headers */, + 88144F891D870F3E007C7F66 /* IGListIndexPathResultInternal.h in Headers */, + 88144F781D870F3E007C7F66 /* IGListMoveIndexPath.h in Headers */, + 88144F801D870F3E007C7F66 /* IGListSupplementaryViewSource.h in Headers */, + 88144F841D870F3E007C7F66 /* IGListAdapterProxy.h in Headers */, + 88144F8A1D870F3E007C7F66 /* IGListIndexSetResultInternal.h in Headers */, + 88144F751D870F3E007C7F66 /* IGListMacros.h in Headers */, + 88144F671D870F3E007C7F66 /* IGListCollectionView.h in Headers */, + 88144F6C1D870F3E007C7F66 /* IGListDisplayDelegate.h in Headers */, + 88144F8B1D870F3E007C7F66 /* IGListItemControllerInternal.h in Headers */, + 88144F811D870F3E007C7F66 /* IGListUpdatingDelegate.h in Headers */, + 88144F6D1D870F3E007C7F66 /* IGListExperiments.h in Headers */, + 88144F5E1D870F3E007C7F66 /* IGListAdapterDataSource.h in Headers */, + 88144F621D870F3E007C7F66 /* IGListAdapterUpdaterDelegate.h in Headers */, + 88144F971D870F3E007C7F66 /* NSObject+IGListDiffable.h in Headers */, + 88144F8E1D870F3E007C7F66 /* IGListMoveIndexInternal.h in Headers */, + 88144F831D870F3E007C7F66 /* IGListAdapterInternal.h in Headers */, + 88144F7E1D870F3E007C7F66 /* IGListStackedItemController.h in Headers */, + 88144F601D870F3E007C7F66 /* IGListAdapterUpdater.h in Headers */, + 88144F661D870F3E007C7F66 /* IGListCollectionContext.h in Headers */, + 88144F911D870F3E007C7F66 /* IGListWorkingRangeHandler.h in Headers */, + 88144F821D870F3E007C7F66 /* IGListWorkingRangeDelegate.h in Headers */, + 88144F951D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.h in Headers */, + 88144F5F1D870F3E007C7F66 /* IGListAdapterDelegate.h in Headers */, + 88144F5C1D870F3E007C7F66 /* IGListAdapter.h in Headers */, + 88144F631D870F3E007C7F66 /* IGListAssert.h in Headers */, + 88144F7A1D870F3E007C7F66 /* IGListReloadDataUpdater.h in Headers */, + 88144F701D870F3E007C7F66 /* IGListIndexSetResult.h in Headers */, + 88144F691D870F3E007C7F66 /* IGListDiff.h in Headers */, + 887D0B531D870DFE009E01F7 /* IGListKit.h in Headers */, + 88144F901D870F3E007C7F66 /* IGListStackedItemControllerInternal.h in Headers */, + 88144F721D870F3E007C7F66 /* IGListItemController.h in Headers */, + 88144F861D870F3E007C7F66 /* IGListAdapterUpdaterInternal.h in Headers */, + 88144F931D870F3E007C7F66 /* NSIndexSet+PrettyDescription.h in Headers */, + 88144F6E1D870F3E007C7F66 /* IGListIndexPathResult.h in Headers */, + 88144F8C1D870F3E007C7F66 /* IGListItemMap.h in Headers */, + 88144F6B1D870F3E007C7F66 /* IGListDiffable.h in Headers */, + 88144F871D870F3E007C7F66 /* IGListDisplayHandler.h in Headers */, + 88144F8F1D870F3E007C7F66 /* IGListMoveIndexPathInternal.h in Headers */, + 88144F761D870F3E007C7F66 /* IGListMoveIndex.h in Headers */, + 88144F741D870F3E007C7F66 /* IGListItemType.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 887D0B351D870D7E009E01F7 /* IGListKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 887D0B4A1D870D7F009E01F7 /* Build configuration list for PBXNativeTarget "IGListKit" */; + buildPhases = ( + 887D0B311D870D7E009E01F7 /* Sources */, + 887D0B321D870D7E009E01F7 /* Frameworks */, + 887D0B331D870D7E009E01F7 /* Headers */, + 887D0B341D870D7E009E01F7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IGListKit; + productName = IGListKit; + productReference = 887D0B361D870D7E009E01F7 /* IGListKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 887D0B3E1D870D7F009E01F7 /* IGListKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 887D0B4D1D870D7F009E01F7 /* Build configuration list for PBXNativeTarget "IGListKitTests" */; + buildPhases = ( + B0F051A91ECEEFE126B7C62C /* [CP] Check Pods Manifest.lock */, + 887D0B3B1D870D7F009E01F7 /* Sources */, + 887D0B3C1D870D7F009E01F7 /* Frameworks */, + 887D0B3D1D870D7F009E01F7 /* Resources */, + F2511489A9CECB6E671403FE /* [CP] Embed Pods Frameworks */, + 372454ABFAEDE42EC902285C /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 887D0B421D870D7F009E01F7 /* PBXTargetDependency */, + ); + name = IGListKitTests; + productName = IGListKitTests; + productReference = 887D0B3F1D870D7F009E01F7 /* IGListKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 887D0B2D1D870D7E009E01F7 /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = IG; + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = Instagram; + TargetAttributes = { + 887D0B351D870D7E009E01F7 = { + CreatedOnToolsVersion = 8.0; + ProvisioningStyle = Automatic; + }; + 887D0B3E1D870D7F009E01F7 = { + CreatedOnToolsVersion = 8.0; + LastSwiftMigration = 0800; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 887D0B301D870D7E009E01F7 /* Build configuration list for PBXProject "IGListKit" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 887D0B2C1D870D7E009E01F7; + productRefGroup = 887D0B371D870D7E009E01F7 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 887D0B351D870D7E009E01F7 /* IGListKit */, + 887D0B3E1D870D7F009E01F7 /* IGListKitTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 887D0B341D870D7E009E01F7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 887D0B3D1D870D7F009E01F7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 372454ABFAEDE42EC902285C /* [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-IGListKitTests/Pods-IGListKitTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + B0F051A91ECEEFE126B7C62C /* [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 # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F2511489A9CECB6E671403FE /* [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-IGListKitTests/Pods-IGListKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 887D0B311D870D7E009E01F7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88144F731D870F3E007C7F66 /* IGListItemController.m in Sources */, + 88144F941D870F3E007C7F66 /* NSIndexSet+PrettyDescription.m in Sources */, + 88144F681D870F3E007C7F66 /* IGListCollectionView.m in Sources */, + 88144F851D870F3E007C7F66 /* IGListAdapterProxy.m in Sources */, + 88144F961D870F3E007C7F66 /* UICollectionView+IGListBatchUpdateData.m in Sources */, + 88144F8D1D870F3E007C7F66 /* IGListItemMap.m in Sources */, + 88144F881D870F3E007C7F66 /* IGListDisplayHandler.m in Sources */, + 88144F7B1D870F3E007C7F66 /* IGListReloadDataUpdater.m in Sources */, + 88144F711D870F3E007C7F66 /* IGListIndexSetResult.m in Sources */, + 88144F7D1D870F3E007C7F66 /* IGListSingleItemController.m in Sources */, + 88144F6A1D870F3E007C7F66 /* IGListDiff.mm in Sources */, + 88144F7F1D870F3E007C7F66 /* IGListStackedItemController.m in Sources */, + 88144F981D870F3E007C7F66 /* NSObject+IGListDiffable.m in Sources */, + 88144F791D870F3E007C7F66 /* IGListMoveIndexPath.m in Sources */, + 88144F771D870F3E007C7F66 /* IGListMoveIndex.m in Sources */, + 88144F921D870F3E007C7F66 /* IGListWorkingRangeHandler.mm in Sources */, + 88144F5D1D870F3E007C7F66 /* IGListAdapter.m in Sources */, + 88144F611D870F3E007C7F66 /* IGListAdapterUpdater.m in Sources */, + 88144F6F1D870F3E007C7F66 /* IGListIndexPathResult.m in Sources */, + 88144F651D870F3E007C7F66 /* IGListBatchUpdateData.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 887D0B3B1D870D7F009E01F7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88144F1C1D870EDC007C7F66 /* IGTestStackedDataSource.m in Sources */, + 88144F181D870EDC007C7F66 /* IGTestDelegateController.m in Sources */, + 88144F0D1D870EDC007C7F66 /* IGListDisplayHandlerTests.m in Sources */, + 88144F1B1D870EDC007C7F66 /* IGTestSingleItemDataSource.m in Sources */, + 88144F0E1D870EDC007C7F66 /* IGListIndexResultTests.m in Sources */, + 88144F171D870EDC007C7F66 /* IGTestCell.m in Sources */, + 88144F141D870EDC007C7F66 /* IGListTestOffsettingLayout.m in Sources */, + 88144F131D870EDC007C7F66 /* IGListTestAdapterDataSource.m in Sources */, + 88144F071D870EDC007C7F66 /* IGListAdapterE2ETests.m in Sources */, + 88144F111D870EDC007C7F66 /* IGListStackItemControllerTests.m in Sources */, + 88144F1A1D870EDC007C7F66 /* IGTestObject.m in Sources */, + 88144F0B1D870EDC007C7F66 /* IGListDiffSwiftTests.swift in Sources */, + 88144F191D870EDC007C7F66 /* IGTestDelegateDataSource.m in Sources */, + 88144F0C1D870EDC007C7F66 /* IGListDiffTests.m in Sources */, + 88144F0A1D870EDC007C7F66 /* IGListBatchUpdateDataTests.m in Sources */, + 88144F101D870EDC007C7F66 /* IGListSingleItemControllerTests.m in Sources */, + 88144F121D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m in Sources */, + 88144F151D870EDC007C7F66 /* IGListTestSection.m in Sources */, + 88144F1D1D870EDC007C7F66 /* IGTestSupplementarySource.m in Sources */, + 88144F081D870EDC007C7F66 /* IGListAdapterTests.m in Sources */, + 88144F161D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.m in Sources */, + 88144F091D870EDC007C7F66 /* IGListAdapterUpdaterTests.m in Sources */, + 88144F0F1D870EDC007C7F66 /* IGListObjectMapTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 887D0B421D870D7F009E01F7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 887D0B351D870D7E009E01F7 /* IGListKit */; + targetProxy = 887D0B411D870D7F009E01F7 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 887D0B481D870D7F009E01F7 /* 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_DOCUMENTATION_COMMENTS = YES; + 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_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 887D0B491D870D7F009E01F7 /* 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_DOCUMENTATION_COMMENTS = YES; + 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_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 887D0B4B1D870D7F009E01F7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.instagram.IGListKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 887D0B4C1D870D7F009E01F7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.instagram.IGListKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 887D0B4E1D870D7F009E01F7 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C4A16046BECFD57B7EB75259 /* Pods-IGListKitTests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.instagram.IGListKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 887D0B4F1D870D7F009E01F7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6BCA3FF59943AD1DAC2077E3 /* Pods-IGListKitTests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.instagram.IGListKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 887D0B301D870D7E009E01F7 /* Build configuration list for PBXProject "IGListKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 887D0B481D870D7F009E01F7 /* Debug */, + 887D0B491D870D7F009E01F7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 887D0B4A1D870D7F009E01F7 /* Build configuration list for PBXNativeTarget "IGListKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 887D0B4B1D870D7F009E01F7 /* Debug */, + 887D0B4C1D870D7F009E01F7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 887D0B4D1D870D7F009E01F7 /* Build configuration list for PBXNativeTarget "IGListKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 887D0B4E1D870D7F009E01F7 /* Debug */, + 887D0B4F1D870D7F009E01F7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 887D0B2D1D870D7E009E01F7 /* Project object */; +} diff --git a/IGListKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/IGListKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..413a817b5 --- /dev/null +++ b/IGListKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/IGListKit.xcodeproj/xcshareddata/xcschemes/IGListKit.xcscheme b/IGListKit.xcodeproj/xcshareddata/xcschemes/IGListKit.xcscheme new file mode 100644 index 000000000..20aac124a --- /dev/null +++ b/IGListKit.xcodeproj/xcshareddata/xcschemes/IGListKit.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IGListKit.xcworkspace/contents.xcworkspacedata b/IGListKit.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..689ce6185 --- /dev/null +++ b/IGListKit.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 000000000..b11e968ea --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,30 @@ +BSD License + +For `IGListKit` software + +Copyright (c) 2016, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PATENTS.md b/PATENTS.md new file mode 100755 index 000000000..9cc399bf9 --- /dev/null +++ b/PATENTS.md @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the `IGListKit` software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook’s rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/Podfile b/Podfile new file mode 100644 index 000000000..94ecabb63 --- /dev/null +++ b/Podfile @@ -0,0 +1,9 @@ +source 'https://github.com/CocoaPods/Specs.git' +use_frameworks! +platform :ios, '8.0' + +workspace 'IGListKit' + +target 'IGListKitTests' do + pod 'OCMock', '~> 3.0' +end diff --git a/Pods/OCMock/License.txt b/Pods/OCMock/License.txt new file mode 100644 index 000000000..f433b1a53 --- /dev/null +++ b/Pods/OCMock/License.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/Pods/OCMock/README.md b/Pods/OCMock/README.md new file mode 100644 index 000000000..948822e60 --- /dev/null +++ b/Pods/OCMock/README.md @@ -0,0 +1,10 @@ +OCMock +====== + +OCMock is an Objective-C implementation of mock objects. + +For downloads, documentation, and support please visit [ocmock.org][]. + +[![Build Status](https://travis-ci.org/erikdoe/ocmock.svg?branch=master)](https://travis-ci.org/erikdoe/ocmock) + + [ocmock.org]: http://ocmock.org/ diff --git a/Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.h b/Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.h new file mode 100644 index 000000000..3db700551 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2006-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface NSInvocation(OCMAdditions) + ++ (NSInvocation *)invocationForBlock:(id)block withArguments:(NSArray *)arguments; + +- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude; + +- (id)getArgumentAtIndexAsObject:(NSInteger)argIndex; + +- (NSString *)invocationDescription; + +- (NSString *)argumentDescriptionAtIndex:(NSInteger)argIndex; + +- (NSString *)objectDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)charDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)unsignedCharDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)intDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)unsignedIntDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)shortDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)unsignedShortDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)longDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)unsignedLongDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)longLongDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)unsignedLongLongDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)doubleDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)floatDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)structDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)pointerDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)cStringDescriptionAtIndex:(NSInteger)anInt; +- (NSString *)selectorDescriptionAtIndex:(NSInteger)anInt; + +@end diff --git a/Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.m b/Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.m new file mode 100644 index 000000000..49a9675eb --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.m @@ -0,0 +1,520 @@ +/* + * Copyright (c) 2006-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "NSInvocation+OCMAdditions.h" +#import "OCMFunctionsPrivate.h" +#import "NSMethodSignature+OCMAdditions.h" + + +@implementation NSInvocation(OCMAdditions) + ++ (NSInvocation *)invocationForBlock:(id)block withArguments:(NSArray *)arguments +{ + NSMethodSignature *sig = [NSMethodSignature signatureForBlock:block]; + NSInvocation *inv = [self invocationWithMethodSignature:sig]; + + NSUInteger numArgsRequired = sig.numberOfArguments - 1; + if((arguments != nil) && ([arguments count] != numArgsRequired)) + [NSException raise:NSInvalidArgumentException format:@"Specified too few arguments for block; expected %lu arguments.", (unsigned long) numArgsRequired]; + + for(NSUInteger i = 0, j = 1; i < numArgsRequired; ++i, ++j) + { + id arg = [arguments objectAtIndex:i]; + [inv setArgumentWithObject:arg atIndex:j]; + } + + return inv; + +} + + +static NSString *const OCMRetainedObjectArgumentsKey = @"OCMRetainedObjectArgumentsKey"; + +- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude +{ + if(objc_getAssociatedObject(self, OCMRetainedObjectArgumentsKey) != nil) + { + // looks like we've retained the arguments already; do nothing else + return; + } + + NSMutableArray *retainedArguments = [[NSMutableArray alloc] init]; + + id target = [self target]; + if((target != nil) && (target != objectToExclude) && !object_isClass(target)) + { + // Bad things will happen if the target is a block since it's not being + // copied. There isn't a very good way to tell if an invocation's target + // is a block though (the argument type at index 0 is always "@" even if + // the target is a Class or block), and in practice it's OK since you + // can't mock a block. + [retainedArguments addObject:target]; + } + + NSUInteger numberOfArguments = [[self methodSignature] numberOfArguments]; + for(NSUInteger index = 2; index < numberOfArguments; index++) + { + const char *argumentType = [[self methodSignature] getArgumentTypeAtIndex:index]; + if(OCMIsObjectType(argumentType) && !OCMIsClassType(argumentType)) + { + id argument; + [self getArgument:&argument atIndex:index]; + if((argument != nil) && (argument != objectToExclude)) + { + if(OCMIsBlockType(argumentType)) + { + // block types need to be copied in case they're stack blocks + id blockArgument = [argument copy]; + [retainedArguments addObject:blockArgument]; + [blockArgument release]; + } + else + { + [retainedArguments addObject:argument]; + } + } + } + } + + const char *returnType = [[self methodSignature] methodReturnType]; + if(OCMIsObjectType(returnType) && !OCMIsClassType(returnType)) + { + id returnValue; + [self getReturnValue:&returnValue]; + if((returnValue != nil) && (returnValue != objectToExclude)) + { + if(OCMIsBlockType(returnType)) + { + id blockReturnValue = [returnValue copy]; + [retainedArguments addObject:blockReturnValue]; + [blockReturnValue release]; + } + else + { + [retainedArguments addObject:returnValue]; + } + } + } + + objc_setAssociatedObject(self, OCMRetainedObjectArgumentsKey, retainedArguments, OBJC_ASSOCIATION_RETAIN); + [retainedArguments release]; +} + + +- (void)setArgumentWithObject:(id)arg atIndex:(NSInteger)idx +{ + const char *typeEncoding = [[self methodSignature] getArgumentTypeAtIndex:idx]; + if((arg == nil) || [arg isKindOfClass:[NSNull class]]) + { + if(typeEncoding[0] == '^') + { + void *nullPtr = NULL; + [self setArgument:&nullPtr atIndex:idx]; + } + else if(typeEncoding[0] == '@') + { + id nilObj = nil; + [self setArgument:&nilObj atIndex:idx]; + } + else if(OCMNumberTypeForObjCType(typeEncoding)) + { + NSUInteger argSize; + NSGetSizeAndAlignment(typeEncoding, NULL, &argSize); + void *argBuffer = calloc(1, argSize); + [self setArgument:argBuffer atIndex:idx]; + free(argBuffer); + } + else + { + [NSException raise:NSInvalidArgumentException format:@"Unable to create default value for type '%s'.", typeEncoding]; + } + } + else if(OCMIsObjectType(typeEncoding)) + { + [self setArgument:&arg atIndex:idx]; + } + else + { + if(![arg isKindOfClass:[NSValue class]]) + [NSException raise:NSInvalidArgumentException format:@"Argument '%@' should be boxed in NSValue.", arg]; + + char const *valEncoding = [arg objCType]; + + /// @note Here we allow any data pointer to be passed as a void pointer and + /// any numerical types to be passed as arguments to the block. + BOOL takesVoidPtr = !strcmp(typeEncoding, "^v") && valEncoding[0] == '^'; + BOOL takesNumber = OCMNumberTypeForObjCType(typeEncoding) && OCMNumberTypeForObjCType(valEncoding); + + if(!takesVoidPtr && !takesNumber && !OCMEqualTypesAllowingOpaqueStructs(typeEncoding, valEncoding)) + [NSException raise:NSInvalidArgumentException format:@"Argument type mismatch; type of argument required is '%s' but type of value provided is '%s'", typeEncoding, valEncoding]; + + NSUInteger argSize; + NSGetSizeAndAlignment(typeEncoding, &argSize, NULL); + void *argBuffer = malloc(argSize); + [arg getValue:argBuffer]; + [self setArgument:argBuffer atIndex:idx]; + free(argBuffer); + } + +} + + +- (id)getArgumentAtIndexAsObject:(NSInteger)argIndex +{ + const char *argType = OCMTypeWithoutQualifiers([[self methodSignature] getArgumentTypeAtIndex:(NSUInteger)argIndex]); + + if((strlen(argType) > 1) && (strchr("{^", argType[0]) == NULL) && (strcmp("@?", argType) != 0)) + [NSException raise:NSInvalidArgumentException format:@"Cannot handle argument type '%s'.", argType]; + + if(OCMIsObjectType(argType)) + { + id value; + [self getArgument:&value atIndex:argIndex]; + return value; + } + + switch(argType[0]) + { + case ':': + { + SEL s = (SEL)0; + [self getArgument:&s atIndex:argIndex]; + return [NSValue valueWithBytes:&s objCType:":"]; + } + case 'i': + { + int value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 's': + { + short value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'l': + { + long value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'q': + { + long long value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'c': + { + char value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'C': + { + unsigned char value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'I': + { + unsigned int value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'S': + { + unsigned short value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'L': + { + unsigned long value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'Q': + { + unsigned long long value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'f': + { + float value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'd': + { + double value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case 'D': + { + long double value; + [self getArgument:&value atIndex:argIndex]; + return [NSValue valueWithBytes:&value objCType:@encode(__typeof__(value))]; + } + case 'B': + { + bool value; + [self getArgument:&value atIndex:argIndex]; + return @(value); + } + case '^': + case '*': + { + void *value = NULL; + [self getArgument:&value atIndex:argIndex]; + return [NSValue valueWithPointer:value]; + } + case '{': // structure + { + NSUInteger argSize; + NSGetSizeAndAlignment([[self methodSignature] getArgumentTypeAtIndex:(NSUInteger)argIndex], &argSize, NULL); + if(argSize == 0) // TODO: Can this happen? Is frameLength a good choice in that case? + argSize = [[self methodSignature] frameLength]; + NSMutableData *argumentData = [[[NSMutableData alloc] initWithLength:argSize] autorelease]; + [self getArgument:[argumentData mutableBytes] atIndex:argIndex]; + return [NSValue valueWithBytes:[argumentData bytes] objCType:argType]; + } + + } + [NSException raise:NSInvalidArgumentException format:@"Argument type '%s' not supported", argType]; + return nil; +} + +- (NSString *)invocationDescription +{ + NSMethodSignature *methodSignature = [self methodSignature]; + NSUInteger numberOfArgs = [methodSignature numberOfArguments]; + + if (numberOfArgs == 2) + return NSStringFromSelector([self selector]); + + NSArray *selectorParts = [NSStringFromSelector([self selector]) componentsSeparatedByString:@":"]; + NSMutableString *description = [[NSMutableString alloc] init]; + NSUInteger i; + for(i = 2; i < numberOfArgs; i++) + { + [description appendFormat:@"%@%@:", (i > 2 ? @" " : @""), [selectorParts objectAtIndex:(i - 2)]]; + [description appendString:[self argumentDescriptionAtIndex:(NSInteger)i]]; + } + + return [description autorelease]; +} + +- (NSString *)argumentDescriptionAtIndex:(NSInteger)argIndex +{ + const char *argType = OCMTypeWithoutQualifiers([[self methodSignature] getArgumentTypeAtIndex:(NSUInteger)argIndex]); + + switch(*argType) + { + case '@': return [self objectDescriptionAtIndex:argIndex]; + case 'B': return [self boolDescriptionAtIndex:argIndex]; + case 'c': return [self charDescriptionAtIndex:argIndex]; + case 'C': return [self unsignedCharDescriptionAtIndex:argIndex]; + case 'i': return [self intDescriptionAtIndex:argIndex]; + case 'I': return [self unsignedIntDescriptionAtIndex:argIndex]; + case 's': return [self shortDescriptionAtIndex:argIndex]; + case 'S': return [self unsignedShortDescriptionAtIndex:argIndex]; + case 'l': return [self longDescriptionAtIndex:argIndex]; + case 'L': return [self unsignedLongDescriptionAtIndex:argIndex]; + case 'q': return [self longLongDescriptionAtIndex:argIndex]; + case 'Q': return [self unsignedLongLongDescriptionAtIndex:argIndex]; + case 'd': return [self doubleDescriptionAtIndex:argIndex]; + case 'f': return [self floatDescriptionAtIndex:argIndex]; + case 'D': return [self longDoubleDescriptionAtIndex:argIndex]; + case '{': return [self structDescriptionAtIndex:argIndex]; + case '^': return [self pointerDescriptionAtIndex:argIndex]; + case '*': return [self cStringDescriptionAtIndex:argIndex]; + case ':': return [self selectorDescriptionAtIndex:argIndex]; + default: return [@""]; // avoid confusion with trigraphs... + } + +} + + +- (NSString *)objectDescriptionAtIndex:(NSInteger)anInt +{ + id object; + + [self getArgument:&object atIndex:anInt]; + if (object == nil) + return @"nil"; + else if(![object isProxy] && [object isKindOfClass:[NSString class]]) + return [NSString stringWithFormat:@"@\"%@\"", [object description]]; + else + // The description cannot be nil, if it is then replace it + return [object description] ?: @""; +} + +- (NSString *)boolDescriptionAtIndex:(NSInteger)anInt +{ + bool value; + [self getArgument:&value atIndex:anInt]; + return value? @"YES" : @"NO"; +} + +- (NSString *)charDescriptionAtIndex:(NSInteger)anInt +{ + unsigned char buffer[128]; + memset(buffer, 0x0, 128); + + [self getArgument:&buffer atIndex:anInt]; + + // If there's only one character in the buffer, and it's 0 or 1, then we have a BOOL + if (buffer[1] == '\0' && (buffer[0] == 0 || buffer[0] == 1)) + return (buffer[0] == 1 ? @"YES" : @"NO"); + else + return [NSString stringWithFormat:@"'%c'", *buffer]; +} + +- (NSString *)unsignedCharDescriptionAtIndex:(NSInteger)anInt +{ + unsigned char buffer[128]; + memset(buffer, 0x0, 128); + + [self getArgument:&buffer atIndex:anInt]; + return [NSString stringWithFormat:@"'%c'", *buffer]; +} + +- (NSString *)intDescriptionAtIndex:(NSInteger)anInt +{ + int intValue; + + [self getArgument:&intValue atIndex:anInt]; + return [NSString stringWithFormat:@"%d", intValue]; +} + +- (NSString *)unsignedIntDescriptionAtIndex:(NSInteger)anInt +{ + unsigned int intValue; + + [self getArgument:&intValue atIndex:anInt]; + return [NSString stringWithFormat:@"%d", intValue]; +} + +- (NSString *)shortDescriptionAtIndex:(NSInteger)anInt +{ + short shortValue; + + [self getArgument:&shortValue atIndex:anInt]; + return [NSString stringWithFormat:@"%hi", shortValue]; +} + +- (NSString *)unsignedShortDescriptionAtIndex:(NSInteger)anInt +{ + unsigned short shortValue; + + [self getArgument:&shortValue atIndex:anInt]; + return [NSString stringWithFormat:@"%hu", shortValue]; +} + +- (NSString *)longDescriptionAtIndex:(NSInteger)anInt +{ + long longValue; + + [self getArgument:&longValue atIndex:anInt]; + return [NSString stringWithFormat:@"%ld", longValue]; +} + +- (NSString *)unsignedLongDescriptionAtIndex:(NSInteger)anInt +{ + unsigned long longValue; + + [self getArgument:&longValue atIndex:anInt]; + return [NSString stringWithFormat:@"%lu", longValue]; +} + +- (NSString *)longLongDescriptionAtIndex:(NSInteger)anInt +{ + long long longLongValue; + + [self getArgument:&longLongValue atIndex:anInt]; + return [NSString stringWithFormat:@"%qi", longLongValue]; +} + +- (NSString *)unsignedLongLongDescriptionAtIndex:(NSInteger)anInt +{ + unsigned long long longLongValue; + + [self getArgument:&longLongValue atIndex:anInt]; + return [NSString stringWithFormat:@"%qu", longLongValue]; +} + +- (NSString *)doubleDescriptionAtIndex:(NSInteger)anInt +{ + double doubleValue; + + [self getArgument:&doubleValue atIndex:anInt]; + return [NSString stringWithFormat:@"%f", doubleValue]; +} + +- (NSString *)floatDescriptionAtIndex:(NSInteger)anInt +{ + float floatValue; + + [self getArgument:&floatValue atIndex:anInt]; + return [NSString stringWithFormat:@"%f", floatValue]; +} + +- (NSString *)longDoubleDescriptionAtIndex:(NSInteger)anInt +{ + long double longDoubleValue; + + [self getArgument:&longDoubleValue atIndex:anInt]; + return [NSString stringWithFormat:@"%Lf", longDoubleValue]; +} + +- (NSString *)structDescriptionAtIndex:(NSInteger)anInt +{ + return [NSString stringWithFormat:@"(%@)", [[self getArgumentAtIndexAsObject:anInt] description]]; +} + +- (NSString *)pointerDescriptionAtIndex:(NSInteger)anInt +{ + void *buffer; + + [self getArgument:&buffer atIndex:anInt]; + return [NSString stringWithFormat:@"%p", buffer]; +} + +- (NSString *)cStringDescriptionAtIndex:(NSInteger)anInt +{ + char buffer[104]; + char *cStringPtr; + + [self getArgument:&cStringPtr atIndex:anInt]; + strlcpy(buffer, cStringPtr, sizeof(buffer)); + strlcpy(buffer + 100, "...", (sizeof(buffer) - 100)); + return [NSString stringWithFormat:@"\"%s\"", buffer]; +} + +- (NSString *)selectorDescriptionAtIndex:(NSInteger)anInt +{ + SEL selectorValue; + + [self getArgument:&selectorValue atIndex:anInt]; + return [NSString stringWithFormat:@"@selector(%@)", NSStringFromSelector(selectorValue)]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.h b/Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.h new file mode 100644 index 000000000..d01f5fdd6 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface NSMethodSignature(OCMAdditions) + ++ (NSMethodSignature *)signatureForDynamicPropertyAccessedWithSelector:(SEL)selector inClass:(Class)aClass; ++ (NSMethodSignature *)signatureForBlock:(id)block; + +- (BOOL)usesSpecialStructureReturn; + +- (NSString *)fullTypeString; +- (const char *)fullObjCTypes; + +@end diff --git a/Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.m b/Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.m new file mode 100644 index 000000000..bc043b29c --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.m @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "NSMethodSignature+OCMAdditions.h" +#import "OCMFunctionsPrivate.h" +#import + + +@implementation NSMethodSignature(OCMAdditions) + +#pragma mark Signatures for dynamic properties + ++ (NSMethodSignature *)signatureForDynamicPropertyAccessedWithSelector:(SEL)selector inClass:(Class)aClass +{ + BOOL isGetter = YES; + objc_property_t property = [self propertyMatchingSelector:selector inClass:aClass isGetter:&isGetter]; + if(property == NULL) + return nil; + + const char *propertyAttributesString = property_getAttributes(property); + NSArray *propertyAttributes = [[NSString stringWithCString:propertyAttributesString + encoding:NSASCIIStringEncoding] componentsSeparatedByString:@","]; + NSString *typeStr = nil; + BOOL isDynamic = NO; + for(NSString *attribute in propertyAttributes) + { + if([attribute isEqualToString:@"D"]) + isDynamic = YES; + else if([attribute hasPrefix:@"T"]) + typeStr = [attribute substringFromIndex:1]; + } + + if(!isDynamic) + return nil; + + NSRange r = [typeStr rangeOfString:@"\""]; // incomplete workaround to deal with structs + if(r.location != NSNotFound) + typeStr = [typeStr substringToIndex:r.location]; + + NSString *sigStringFormat = isGetter ? @"%@@:" : @"v@:%@"; + const char *sigCString = [[NSString stringWithFormat:sigStringFormat, typeStr] cStringUsingEncoding:NSASCIIStringEncoding]; + return [NSMethodSignature signatureWithObjCTypes:sigCString]; +} + + ++ (objc_property_t)propertyMatchingSelector:(SEL)selector inClass:(Class)aClass isGetter:(BOOL *)isGetterPtr +{ + NSString *propertyName = NSStringFromSelector(selector); + + // first try selector as is aassuming it's a getter + objc_property_t property = class_getProperty(aClass, [propertyName cStringUsingEncoding:NSASCIIStringEncoding]); + if(property != NULL) + { + *isGetterPtr = YES; + return property; + } + + // try setter next if selector starts with "set" + if([propertyName hasPrefix:@"set"]) + { + propertyName = [propertyName substringFromIndex:@"set".length]; + propertyName = [propertyName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[propertyName substringToIndex:1] lowercaseString]]; + if([propertyName hasSuffix:@":"]) + propertyName = [propertyName substringToIndex:[propertyName length] - 1]; + + property = class_getProperty(aClass, [propertyName cStringUsingEncoding:NSASCIIStringEncoding]); + if(property != NULL) + { + *isGetterPtr = NO; + return property; + } + } + + // search through properties with custom getter/setter that corresponds to selector + unsigned int propertiesCount = 0; + objc_property_t *allProperties = class_copyPropertyList(aClass, &propertiesCount); + for(unsigned int i = 0 ; i < propertiesCount; i++) + { + NSArray *propertyAttributes = [[NSString stringWithCString:property_getAttributes(allProperties[i]) + encoding:NSASCIIStringEncoding] componentsSeparatedByString:@","]; + for(NSString *attribute in propertyAttributes) + { + if(([attribute hasPrefix:@"G"] || [attribute hasPrefix:@"S"]) && + [[attribute substringFromIndex:1] isEqualToString:propertyName]) + { + *isGetterPtr = ![attribute hasPrefix:@"S"]; + property = allProperties[i]; + i = propertiesCount; + break; + } + } + } + free(allProperties); + + return property; +} + + +#pragma mark Signatures for blocks + +struct OCMBlockDef +{ + void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock + int flags; + int reserved; + void (*invoke)(void *, ...); + struct block_descriptor { + unsigned long int reserved; // NULL + unsigned long int size; // sizeof(struct Block_literal_1) + // optional helper functions + void (*copy_helper)(void *dst, void *src); // IFF (1<<25) + void (*dispose_helper)(void *src); // IFF (1<<25) + // required ABI.2010.3.16 + const char *signature; // IFF (1<<30) + } *descriptor; +}; + +enum +{ + OCMBlockDescriptionFlagsHasCopyDispose = (1 << 25), + OCMBlockDescriptionFlagsHasSignature = (1 << 30) +}; + + ++ (NSMethodSignature *)signatureForBlock:(id)block +{ + /* For a more complete implementation of parsing the block data structure see: + * + * https://github.com/ebf/CTObjectiveCRuntimeAdditions/tree/master/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions + */ + + struct OCMBlockDef *blockRef = (__bridge struct OCMBlockDef *)block; + + if(!(blockRef->flags & OCMBlockDescriptionFlagsHasSignature)) + return nil; + + void *signatureLocation = blockRef->descriptor; + signatureLocation += sizeof(unsigned long int); + signatureLocation += sizeof(unsigned long int); + if(blockRef->flags & OCMBlockDescriptionFlagsHasCopyDispose) + { + signatureLocation += sizeof(void(*)(void *dst, void *src)); + signatureLocation += sizeof(void (*)(void *src)); + } + + const char *signature = (*(const char **)signatureLocation); + return [NSMethodSignature signatureWithObjCTypes:signature]; +} + + +#pragma mark Extended attributes + +- (BOOL)usesSpecialStructureReturn +{ + const char *types = OCMTypeWithoutQualifiers([self methodReturnType]); + + if((types == NULL) || (types[0] != '{')) + return NO; + + /* In some cases structures are returned by ref. The rules are complex and depend on the + architecture, see: + + http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html + http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html + https://github.com/atgreen/libffi/blob/master/src/x86/ffi64.c + http://www.uclibc.org/docs/psABI-x86_64.pdf + http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf + + NSMethodSignature knows the details but has no API to return it, though it is in + the debugDescription. Horribly kludgy. + */ + NSRange range = [[self debugDescription] rangeOfString:@"is special struct return? YES"]; + return range.length > 0; +} + + +- (NSString *)fullTypeString +{ + NSMutableString *typeString = [NSMutableString string]; + [typeString appendFormat:@"%s", [self methodReturnType]]; + for (NSUInteger i=0; i<[self numberOfArguments]; i++) + [typeString appendFormat:@"%s", [self getArgumentTypeAtIndex:i]]; + return typeString; +} + + +- (const char *)fullObjCTypes +{ + return [[self fullTypeString] UTF8String]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.h b/Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.h new file mode 100644 index 000000000..7d58aabea --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCObserverMockObject; + + +@interface NSNotificationCenter(OCMAdditions) + +- (void)addMockObserver:(OCObserverMockObject *)notificationObserver name:(NSString *)notificationName object:(id)notificationSender; + +@end diff --git a/Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.m b/Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.m new file mode 100644 index 000000000..f758764ac --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.m @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "NSNotificationCenter+OCMAdditions.h" +#import "OCObserverMockObject.h" + + +@implementation NSNotificationCenter(OCMAdditions) + +- (void)addMockObserver:(OCObserverMockObject *)notificationObserver name:(NSString *)notificationName object:(id)notificationSender +{ + [notificationObserver autoRemoveFromCenter:self]; + [self addObserver:notificationObserver selector:@selector(handleNotification:) name:notificationName object:notificationSender]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.h b/Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.h new file mode 100644 index 000000000..519fde90c --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2013-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface NSObject(OCMAdditions) + ++ (IMP)instanceMethodForwarderForSelector:(SEL)aSelector; ++ (void)enumerateMethodsInClass:(Class)aClass usingBlock:(void (^)(Class cls, SEL sel))aBlock; + +@end diff --git a/Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.m b/Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.m new file mode 100644 index 000000000..8f3c39111 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.m @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "NSObject+OCMAdditions.h" +#import "NSMethodSignature+OCMAdditions.h" +#import + +@implementation NSObject(OCMAdditions) + ++ (IMP)instanceMethodForwarderForSelector:(SEL)aSelector +{ + // use sel_registerName() and not @selector to avoid warning + SEL selectorWithNoImplementation = sel_registerName("methodWhichMustNotExist::::"); + +#ifndef __arm64__ + static NSMutableDictionary *_OCMReturnTypeCache; + + if(_OCMReturnTypeCache == nil) + _OCMReturnTypeCache = [[NSMutableDictionary alloc] init]; + + BOOL needsStructureReturn; + void *rawCacheKey[2] = { (void *)self, aSelector }; + NSData *cacheKey = [NSData dataWithBytes:rawCacheKey length:sizeof(rawCacheKey)]; + NSNumber *cachedValue = [_OCMReturnTypeCache objectForKey:cacheKey]; + + if(cachedValue == nil) + { + NSMethodSignature *sig = [self instanceMethodSignatureForSelector:aSelector]; + needsStructureReturn = [sig usesSpecialStructureReturn]; + [_OCMReturnTypeCache setObject:@(needsStructureReturn) forKey:cacheKey]; + } + else + { + needsStructureReturn = [cachedValue boolValue]; + } + + if(needsStructureReturn) + return class_getMethodImplementation_stret([NSObject class], selectorWithNoImplementation); +#endif + + return class_getMethodImplementation([NSObject class], selectorWithNoImplementation); +} + + ++ (void)enumerateMethodsInClass:(Class)aClass usingBlock:(void (^)(Class cls, SEL sel))aBlock +{ + for(Class cls = aClass; cls != nil; cls = class_getSuperclass(cls)) + { + Method *methodList = class_copyMethodList(cls, NULL); + if(methodList == NULL) + continue; + + for(Method *mPtr = methodList; *mPtr != NULL; mPtr++) + { + SEL sel = method_getName(*mPtr); + aBlock(cls, sel); + } + free(methodList); + } +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.h b/Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.h new file mode 100644 index 000000000..d6977af04 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface NSValue(OCMAdditions) + +- (BOOL)getBytes:(void *)outputBuf objCType:(const char *)targetType; + +@end diff --git a/Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.m b/Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.m new file mode 100644 index 000000000..b3aa293b6 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.m @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "NSValue+OCMAdditions.h" +#import "OCMFunctionsPrivate.h" + +@implementation NSValue(OCMAdditions) + +static NSNumber *OCMNumberForValue(NSValue *value) +{ +#define CREATE_NUM(_type) ({ _type _v; [value getValue:&_v]; @(_v); }) + switch([value objCType][0]) + { + case 'c': return CREATE_NUM(char); + case 'C': return CREATE_NUM(unsigned char); + case 'B': return CREATE_NUM(bool); + case 's': return CREATE_NUM(short); + case 'S': return CREATE_NUM(unsigned short); + case 'i': return CREATE_NUM(int); + case 'I': return CREATE_NUM(unsigned int); + case 'l': return CREATE_NUM(long); + case 'L': return CREATE_NUM(unsigned long); + case 'q': return CREATE_NUM(long long); + case 'Q': return CREATE_NUM(unsigned long long); + case 'f': return CREATE_NUM(float); + case 'd': return CREATE_NUM(double); + default: return nil; + } +} + + +- (BOOL)getBytes:(void *)outputBuf objCType:(const char *)targetType +{ + /* + * See if they are similar number types, and if we can convert losslessly between them. + * For the most part, we set things up to use CFNumberGetValue, which returns false if + * conversion will be lossy. + */ + CFNumberType inputType = OCMNumberTypeForObjCType([self objCType]); + CFNumberType outputType = OCMNumberTypeForObjCType(targetType); + + if(inputType == 0 || outputType == 0) // one or both are non-number types + return NO; + + NSNumber *inputNumber = [self isKindOfClass:[NSNumber class]] ? (NSNumber *)self : OCMNumberForValue(self); + + /* + * Due to some legacy, back-compatible requirements in CFNumber.c, CFNumberGetValue can return true for + * some conversions which should not be allowed (by reading source, conversions from integer types to + * 8-bit or 16-bit integer types). So, check ourselves. + */ + long long min; + long long max; + long long val = [inputNumber longLongValue]; + switch(targetType[0]) + { + case 'B': + case 'c': min = CHAR_MIN; max = CHAR_MAX; break; + case 'C': min = 0; max = UCHAR_MAX; break; + case 's': min = SHRT_MIN; max = SHRT_MAX; break; + case 'S': min = 0; max = USHRT_MAX; break; + default: min = LLONG_MIN; max = LLONG_MAX; break; + } + if(val < min || val > max) + return NO; + + /* Get the number, and return NO if the value was out of range or conversion was lossy */ + return CFNumberGetValue((CFNumberRef)inputNumber, outputType, outputBuf); +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCClassMockObject.h b/Pods/OCMock/Source/OCMock/OCClassMockObject.h new file mode 100644 index 000000000..29c67f1a7 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCClassMockObject.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2005-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCClassMockObject : OCMockObject +{ + Class mockedClass; + Class originalMetaClass; +} + +- (id)initWithClass:(Class)aClass; + +- (Class)mockedClass; +- (Class)mockObjectClass; // since -class returns the mockedClass + +@end diff --git a/Pods/OCMock/Source/OCMock/OCClassMockObject.m b/Pods/OCMock/Source/OCMock/OCClassMockObject.m new file mode 100644 index 000000000..8770a6322 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCClassMockObject.m @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2005-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCClassMockObject.h" +#import "NSObject+OCMAdditions.h" +#import "OCMFunctionsPrivate.h" +#import "OCMInvocationStub.h" +#import "NSMethodSignature+OCMAdditions.h" + +@implementation OCClassMockObject + +#pragma mark Initialisers, description, accessors, etc. + +- (id)initWithClass:(Class)aClass +{ + NSParameterAssert(aClass != nil); + [super init]; + mockedClass = aClass; + [self prepareClassForClassMethodMocking]; + return self; +} + +- (void)dealloc +{ + [self stopMocking]; + [super dealloc]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"OCMockObject(%@)", NSStringFromClass(mockedClass)]; +} + +- (Class)mockedClass +{ + return mockedClass; +} + +#pragma mark Extending/overriding superclass behaviour + +- (void)stopMocking +{ + if(originalMetaClass != nil) + { + /* The mocked class has the meta class of a dynamically created subclass as its meta class, + but we need a reference to the subclass to dispose it. Asking the meta class for its + class name returns the actual class name, which we can then use to look up the class... + */ + const char *createdSubclassName = object_getClassName(mockedClass); + Class createdSubclass = objc_lookUpClass(createdSubclassName); + + [self restoreMetaClass]; + + objc_disposeClassPair(createdSubclass); + } + [super stopMocking]; +} + +- (void)restoreMetaClass +{ + OCMSetAssociatedMockForClass(nil, mockedClass); + object_setClass(mockedClass, originalMetaClass); + originalMetaClass = nil; +} + +- (void)addStub:(OCMInvocationStub *)aStub +{ + [super addStub:aStub]; + if([aStub recordedAsClassMethod]) + [self setupForwarderForClassMethodSelector:[[aStub recordedInvocation] selector]]; +} + + +#pragma mark Class method mocking + +- (void)prepareClassForClassMethodMocking +{ + /* the runtime and OCMock depend on string and array; we don't intercept methods on them to avoid endless loops */ + if([[mockedClass class] isSubclassOfClass:[NSString class]] || [[mockedClass class] isSubclassOfClass:[NSArray class]]) + return; + + /* if there is another mock for this exact class, stop it */ + id otherMock = OCMGetAssociatedMockForClass(mockedClass, NO); + if(otherMock != nil) + [otherMock restoreMetaClass]; + + OCMSetAssociatedMockForClass(self, mockedClass); + + /* dynamically create a subclass and use its meta class as the meta class for the mocked class */ + Class subclass = OCMCreateSubclass(mockedClass, mockedClass); + originalMetaClass = object_getClass(mockedClass); + id newMetaClass = object_getClass(subclass); + + /* create a dummy initialize method */ + Method myDummyInitializeMethod = class_getInstanceMethod([self mockObjectClass], @selector(initializeForClassObject)); + const char *initializeTypes = method_getTypeEncoding(myDummyInitializeMethod); + IMP myDummyInitializeIMP = method_getImplementation(myDummyInitializeMethod); + class_addMethod(newMetaClass, @selector(initialize), myDummyInitializeIMP, initializeTypes); + + object_setClass(mockedClass, newMetaClass); // only after dummy initialize is installed (iOS9) + + /* point forwardInvocation: of the object to the implementation in the mock */ + Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForClassObject:)); + IMP myForwardIMP = method_getImplementation(myForwardMethod); + class_addMethod(newMetaClass, @selector(forwardInvocation:), myForwardIMP, method_getTypeEncoding(myForwardMethod)); + + + /* adding forwarder for most class methods (instance methods on meta class) to allow for verify after run */ + NSArray *methodBlackList = @[@"class", @"forwardingTargetForSelector:", @"methodSignatureForSelector:", @"forwardInvocation:", @"isBlock", + @"instanceMethodForwarderForSelector:", @"instanceMethodSignatureForSelector:"]; + [NSObject enumerateMethodsInClass:originalMetaClass usingBlock:^(Class cls, SEL sel) { + if((cls == object_getClass([NSObject class])) || (cls == [NSObject class]) || (cls == object_getClass(cls))) + return; + NSString *className = NSStringFromClass(cls); + NSString *selName = NSStringFromSelector(sel); + if(([className hasPrefix:@"NS"] || [className hasPrefix:@"UI"]) && + ([selName hasPrefix:@"_"] || [selName hasSuffix:@"_"])) + return; + if([methodBlackList containsObject:selName]) + return; + @try + { + [self setupForwarderForClassMethodSelector:sel]; + } + @catch(NSException *e) + { + // ignore for now + } + }]; +} + +- (void)setupForwarderForClassMethodSelector:(SEL)selector +{ + SEL aliasSelector = OCMAliasForOriginalSelector(selector); + if(class_getClassMethod(mockedClass, aliasSelector) != NULL) + return; + + Method originalMethod = class_getClassMethod(mockedClass, selector); + IMP originalIMP = method_getImplementation(originalMethod); + const char *types = method_getTypeEncoding(originalMethod); + + Class metaClass = object_getClass(mockedClass); + IMP forwarderIMP = [originalMetaClass instanceMethodForwarderForSelector:selector]; + class_replaceMethod(metaClass, selector, forwarderIMP, types); + class_addMethod(metaClass, aliasSelector, originalIMP, types); +} + + +- (void)forwardInvocationForClassObject:(NSInvocation *)anInvocation +{ + // in here "self" is a reference to the real class, not the mock + OCClassMockObject *mock = OCMGetAssociatedMockForClass((Class) self, YES); + if(mock == nil) + { + [NSException raise:NSInternalInconsistencyException format:@"No mock for class %@", NSStringFromClass((Class)self)]; + } + if([mock handleInvocation:anInvocation] == NO) + { + [anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation selector])]; + [anInvocation invoke]; + } +} + +- (void)initializeForClassObject +{ + // we really just want to have an implementation so that the superclass's is not called +} + + +#pragma mark Proxy API + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + NSMethodSignature *signature = [mockedClass instanceMethodSignatureForSelector:aSelector]; + if(signature == nil) + { + signature = [NSMethodSignature signatureForDynamicPropertyAccessedWithSelector:aSelector inClass:mockedClass]; + } + return signature; +} + +- (Class)mockObjectClass +{ + return [super class]; +} + +- (Class)class +{ + return mockedClass; +} + +- (BOOL)respondsToSelector:(SEL)selector +{ + return [mockedClass instancesRespondToSelector:selector]; +} + +- (BOOL)isKindOfClass:(Class)aClass +{ + return [mockedClass isSubclassOfClass:aClass]; +} + +- (BOOL)conformsToProtocol:(Protocol *)aProtocol +{ + return class_conformsToProtocol(mockedClass, aProtocol); +} + +@end + + +#pragma mark - + +/** + taken from: + `class-dump -f isNS /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/CoreFoundation.framework` + + @interface NSObject (__NSIsKinds) + - (_Bool)isNSValue__; + - (_Bool)isNSTimeZone__; + - (_Bool)isNSString__; + - (_Bool)isNSSet__; + - (_Bool)isNSOrderedSet__; + - (_Bool)isNSNumber__; + - (_Bool)isNSDictionary__; + - (_Bool)isNSDate__; + - (_Bool)isNSData__; + - (_Bool)isNSArray__; + */ + +@implementation OCClassMockObject(NSIsKindsImplementation) + +- (BOOL)isNSValue__ +{ + return [mockedClass isSubclassOfClass:[NSValue class]]; +} + +- (BOOL)isNSTimeZone__ +{ + return [mockedClass isSubclassOfClass:[NSTimeZone class]]; +} + +- (BOOL)isNSSet__ +{ + return [mockedClass isSubclassOfClass:[NSSet class]]; +} + +- (BOOL)isNSOrderedSet__ +{ + return [mockedClass isSubclassOfClass:[NSOrderedSet class]]; +} + +- (BOOL)isNSNumber__ +{ + return [mockedClass isSubclassOfClass:[NSNumber class]]; +} + +- (BOOL)isNSDate__ +{ + return [mockedClass isSubclassOfClass:[NSDate class]]; +} + +- (BOOL)isNSString__ +{ + return [mockedClass isSubclassOfClass:[NSString class]]; +} + +- (BOOL)isNSDictionary__ +{ + return [mockedClass isSubclassOfClass:[NSDictionary class]]; +} + +- (BOOL)isNSData__ +{ + return [mockedClass isSubclassOfClass:[NSData class]]; +} + +- (BOOL)isNSArray__ +{ + return [mockedClass isSubclassOfClass:[NSArray class]]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMArg.h b/Pods/OCMock/Source/OCMock/OCMArg.h new file mode 100644 index 000000000..6df735e99 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMArg.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMArg : NSObject + +// constraining arguments + ++ (id)any; ++ (SEL)anySelector; ++ (void *)anyPointer; ++ (id __autoreleasing *)anyObjectRef; ++ (id)isNil; ++ (id)isNotNil; ++ (id)isEqual:(id)value; ++ (id)isNotEqual:(id)value; ++ (id)isKindOfClass:(Class)cls; ++ (id)checkWithSelector:(SEL)selector onObject:(id)anObject; ++ (id)checkWithBlock:(BOOL (^)(id obj))block; + +// manipulating arguments + ++ (id *)setTo:(id)value; ++ (void *)setToValue:(NSValue *)value; ++ (id)invokeBlock; ++ (id)invokeBlockWithArgs:(id)first,... NS_REQUIRES_NIL_TERMINATION; + ++ (id)defaultValue; + +// internal use only + ++ (id)resolveSpecialValues:(NSValue *)value; + +@end + +#define OCMOCK_ANY [OCMArg any] + +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) + #define OCMOCK_VALUE(variable) \ + ({ __typeof__(variable) __v = (variable); [NSValue value:&__v withObjCType:@encode(__typeof__(__v))]; }) +#else + #define OCMOCK_VALUE(variable) [NSValue value:&variable withObjCType:@encode(__typeof__(variable))] +#endif + diff --git a/Pods/OCMock/Source/OCMock/OCMArg.m b/Pods/OCMock/Source/OCMock/OCMArg.m new file mode 100644 index 000000000..6a90120a9 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMArg.m @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import +#import +#import "OCMPassByRefSetter.h" +#import "OCMBlockArgCaller.h" + +@implementation OCMArg + ++ (id)any +{ + return [OCMAnyConstraint constraint]; +} + ++ (void *)anyPointer +{ + return (void *)0x01234567; +} + ++ (id __autoreleasing *)anyObjectRef +{ + return (id *)0x01234567; +} + ++ (SEL)anySelector +{ + return NSSelectorFromString(@"aSelectorThatMatchesAnySelector"); +} + ++ (id)isNil +{ + return [OCMIsNilConstraint constraint]; +} + ++ (id)isNotNil +{ + return [OCMIsNotNilConstraint constraint]; +} + ++ (id)isEqual:(id)value +{ + return value; +} + ++ (id)isNotEqual:(id)value +{ + OCMIsNotEqualConstraint *constraint = [OCMIsNotEqualConstraint constraint]; + constraint->testValue = value; + return constraint; +} + ++ (id)isKindOfClass:(Class)cls +{ + return [[[OCMBlockConstraint alloc] initWithConstraintBlock:^BOOL(id obj) { + return [obj isKindOfClass:cls]; + }] autorelease]; +} + ++ (id)checkWithSelector:(SEL)selector onObject:(id)anObject +{ + return [OCMConstraint constraintWithSelector:selector onObject:anObject]; +} + ++ (id)checkWithBlock:(BOOL (^)(id))block +{ + return [[[OCMBlockConstraint alloc] initWithConstraintBlock:block] autorelease]; +} + ++ (id *)setTo:(id)value +{ + return (id *)[[[OCMPassByRefSetter alloc] initWithValue:value] autorelease]; +} + ++ (void *)setToValue:(NSValue *)value +{ + return (id *)[[[OCMPassByRefSetter alloc] initWithValue:value] autorelease]; +} + ++ (id)invokeBlock +{ + return [[[OCMBlockArgCaller alloc] init] autorelease]; +} + ++ (id)invokeBlockWithArgs:(id)first,... NS_REQUIRES_NIL_TERMINATION +{ + + NSMutableArray *params = [NSMutableArray array]; + va_list args; + if(first) + { + [params addObject:first]; + va_start(args, first); + id obj; + while((obj = va_arg(args, id))) + { + [params addObject:obj]; + } + va_end(args); + } + return [[[OCMBlockArgCaller alloc] initWithBlockArguments:params] autorelease]; + +} + ++ (id)defaultValue +{ + return [NSNull null]; +} + + ++ (id)resolveSpecialValues:(NSValue *)value +{ + const char *type = [value objCType]; + if(type[0] == '^') + { + void *pointer = [value pointerValue]; + if(pointer == (void *)0x01234567) + return [OCMArg any]; + if((pointer != NULL) && (object_getClass((id)pointer) == [OCMPassByRefSetter class])) + return (id)pointer; + } + else if(type[0] == ':') + { + SEL selector; + [value getValue:&selector]; + if(selector == NSSelectorFromString(@"aSelectorThatMatchesAnySelector")) + return [OCMArg any]; + } + return value; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMArgAction.h b/Pods/OCMock/Source/OCMock/OCMArgAction.h new file mode 100644 index 000000000..15fc62cf2 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMArgAction.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMArgAction : NSObject + +- (void)handleArgument:(id)argument; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMArgAction.m b/Pods/OCMock/Source/OCMock/OCMArgAction.m new file mode 100644 index 000000000..ff1b83135 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMArgAction.m @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2015-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMArgAction.h" + + +@implementation OCMArgAction + +- (void)handleArgument:(id)argument +{ + +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMBlockArgCaller.h b/Pods/OCMock/Source/OCMock/OCMBlockArgCaller.h new file mode 100644 index 000000000..3c63a91f8 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMBlockArgCaller.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMArgAction.h" + +@interface OCMBlockArgCaller : OCMArgAction +{ + NSArray *arguments; +} + +- (instancetype)initWithBlockArguments:(NSArray *)someArgs; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMBlockArgCaller.m b/Pods/OCMock/Source/OCMock/OCMBlockArgCaller.m new file mode 100644 index 000000000..aac0618ed --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMBlockArgCaller.m @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMBlockArgCaller.h" +#import "NSInvocation+OCMAdditions.h" + + +@implementation OCMBlockArgCaller + +- (instancetype)initWithBlockArguments:(NSArray *)someArgs +{ + self = [super init]; + if(self) + { + arguments = [someArgs copy]; + } + return self; +} + +- (void)dealloc +{ + [arguments release]; + [super dealloc]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + return [self retain]; +} + +- (void)handleArgument:(id)aBlock +{ + if(aBlock) + { + NSInvocation *inv = [NSInvocation invocationForBlock:aBlock withArguments:arguments]; + [inv invokeWithTarget:aBlock]; + } +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMBlockCaller.h b/Pods/OCMock/Source/OCMock/OCMBlockCaller.h new file mode 100644 index 000000000..fab09a310 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMBlockCaller.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + + +@interface OCMBlockCaller : NSObject +{ + void (^block)(NSInvocation *); +} + +- (id)initWithCallBlock:(void (^)(NSInvocation *))theBlock; + +- (void)handleInvocation:(NSInvocation *)anInvocation; + +@end + diff --git a/Pods/OCMock/Source/OCMock/OCMBlockCaller.m b/Pods/OCMock/Source/OCMock/OCMBlockCaller.m new file mode 100644 index 000000000..a5aaea874 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMBlockCaller.m @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMBlockCaller.h" + + +@implementation OCMBlockCaller + +-(id)initWithCallBlock:(void (^)(NSInvocation *))theBlock +{ + if ((self = [super init])) + { + block = [theBlock copy]; + } + + return self; +} + +-(void)dealloc +{ + [block release]; + [super dealloc]; +} + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + if (block != nil) + { + block(anInvocation); + } +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h b/Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h new file mode 100644 index 000000000..f4728a235 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMReturnValueProvider.h" + +@interface OCMBoxedReturnValueProvider : OCMReturnValueProvider +{ +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.m b/Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.m new file mode 100644 index 000000000..b2b016ffd --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.m @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMBoxedReturnValueProvider.h" +#import "OCMFunctionsPrivate.h" +#import "NSValue+OCMAdditions.h" + +@implementation OCMBoxedReturnValueProvider + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + const char *returnType = [[anInvocation methodSignature] methodReturnType]; + NSUInteger returnTypeSize = [[anInvocation methodSignature] methodReturnLength]; + char valueBuffer[returnTypeSize]; + NSValue *returnValueAsNSValue = (NSValue *)returnValue; + + if([self isMethodReturnType:returnType compatibleWithValueType:[returnValueAsNSValue objCType]]) + { + [returnValueAsNSValue getValue:valueBuffer]; + [anInvocation setReturnValue:valueBuffer]; + } + else if([returnValueAsNSValue getBytes:valueBuffer objCType:returnType]) + { + [anInvocation setReturnValue:valueBuffer]; + } + else + { + [NSException raise:NSInvalidArgumentException + format:@"Return value cannot be used for method; method signature declares '%s' but value is '%s'.", returnType, [returnValueAsNSValue objCType]]; + } +} + + +- (BOOL)isMethodReturnType:(const char *)returnType compatibleWithValueType:(const char *)valueType +{ + /* Same types are obviously compatible */ + if(strcmp(returnType, valueType) == 0) + return YES; + + /* Allow void* for methods that return id, mainly to be able to handle nil */ + if(strcmp(returnType, @encode(id)) == 0 && strcmp(valueType, @encode(void *)) == 0) + return YES; + + return OCMEqualTypesAllowingOpaqueStructs(returnType, valueType); +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMConstraint.h b/Pods/OCMock/Source/OCMock/OCMConstraint.h new file mode 100644 index 000000000..19fc1a713 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMConstraint.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2007-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + + +@interface OCMConstraint : NSObject + ++ (instancetype)constraint; +- (BOOL)evaluate:(id)value; + +// if you are looking for any, isNil, etc, they have moved to OCMArg + +// try to use [OCMArg checkWith...] instead of the constraintWith... methods below + ++ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject; ++ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue; + + +@end + +@interface OCMAnyConstraint : OCMConstraint +@end + +@interface OCMIsNilConstraint : OCMConstraint +@end + +@interface OCMIsNotNilConstraint : OCMConstraint +@end + +@interface OCMIsNotEqualConstraint : OCMConstraint +{ + @public + id testValue; +} + +@end + +@interface OCMInvocationConstraint : OCMConstraint +{ + @public + NSInvocation *invocation; +} + +@end + +@interface OCMBlockConstraint : OCMConstraint +{ + BOOL (^block)(id); +} + +- (instancetype)initWithConstraintBlock:(BOOL (^)(id))block; + +@end + + +#define CONSTRAINT(aSelector) [OCMConstraint constraintWithSelector:aSelector onObject:self] +#define CONSTRAINTV(aSelector, aValue) [OCMConstraint constraintWithSelector:aSelector onObject:self withValue:(aValue)] diff --git a/Pods/OCMock/Source/OCMock/OCMConstraint.m b/Pods/OCMock/Source/OCMock/OCMConstraint.m new file mode 100644 index 000000000..cc1204f83 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMConstraint.m @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2007-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + + +@implementation OCMConstraint + ++ (instancetype)constraint +{ + return [[[self alloc] init] autorelease]; +} + +- (BOOL)evaluate:(id)value +{ + return NO; +} + +- (id)copyWithZone:(struct _NSZone *)zone +{ + return [self retain]; +} + ++ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject +{ + OCMInvocationConstraint *constraint = [OCMInvocationConstraint constraint]; + NSMethodSignature *signature = [anObject methodSignatureForSelector:aSelector]; + if(signature == nil) + [NSException raise:NSInvalidArgumentException format:@"Unkown selector %@ used in constraint.", NSStringFromSelector(aSelector)]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setTarget:anObject]; + [invocation setSelector:aSelector]; + constraint->invocation = invocation; + return constraint; +} + ++ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue +{ + OCMInvocationConstraint *constraint = [self constraintWithSelector:aSelector onObject:anObject]; + if([[constraint->invocation methodSignature] numberOfArguments] < 4) + [NSException raise:NSInvalidArgumentException format:@"Constraint with value requires selector with two arguments."]; + [constraint->invocation setArgument:&aValue atIndex:3]; + return constraint; +} + + +@end + + + +#pragma mark - + +@implementation OCMAnyConstraint + +- (BOOL)evaluate:(id)value +{ + return YES; +} + +@end + + + +#pragma mark - + +@implementation OCMIsNilConstraint + +- (BOOL)evaluate:(id)value +{ + return value == nil; +} + +@end + + + +#pragma mark - + +@implementation OCMIsNotNilConstraint + +- (BOOL)evaluate:(id)value +{ + return value != nil; +} + +@end + + + +#pragma mark - + +@implementation OCMIsNotEqualConstraint + +- (BOOL)evaluate:(id)value +{ + return ![value isEqual:testValue]; +} + +@end + + + +#pragma mark - + +@implementation OCMInvocationConstraint + +- (BOOL)evaluate:(id)value +{ + [invocation setArgument:&value atIndex:2]; // should test if constraint takes arg + [invocation invoke]; + BOOL returnValue; + [invocation getReturnValue:&returnValue]; + return returnValue; +} + +@end + +#pragma mark - + +@implementation OCMBlockConstraint + +- (instancetype)initWithConstraintBlock:(BOOL (^)(id))aBlock +{ + if ((self = [super init])) + { + block = [aBlock copy]; + } + + return self; +} + +- (void)dealloc { + [block release]; + [super dealloc]; +} + +- (BOOL)evaluate:(id)value +{ + return block ? block(value) : NO; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h b/Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h new file mode 100644 index 000000000..793c4fdeb --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMReturnValueProvider.h" + +extern NSString *OCMStubbedException; + +@interface OCMExceptionReturnValueProvider : OCMReturnValueProvider +{ +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.m b/Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.m new file mode 100644 index 000000000..17dad5dcb --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.m @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMExceptionReturnValueProvider.h" + + +@implementation OCMExceptionReturnValueProvider + +NSString *OCMStubbedException = @"OCMStubbedException"; + + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + [[NSException exceptionWithName:OCMStubbedException reason:@"Exception stubbed in test." userInfo:@{ @"exception": returnValue }] raise]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMExpectationRecorder.h b/Pods/OCMock/Source/OCMock/OCMExpectationRecorder.h new file mode 100644 index 000000000..d6de3c09a --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMExpectationRecorder.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2004-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMExpectationRecorder : OCMStubRecorder + +- (id)never; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMExpectationRecorder.m b/Pods/OCMock/Source/OCMock/OCMExpectationRecorder.m new file mode 100644 index 000000000..07f13d1cd --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMExpectationRecorder.m @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2004-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMExpectationRecorder.h" +#import "OCMInvocationExpectation.h" + +@implementation OCMExpectationRecorder + +#pragma mark Initialisers, description, accessors, etc. + +- (id)init +{ + self = [super init]; + [invocationMatcher release]; + invocationMatcher = [[OCMInvocationExpectation alloc] init]; + return self; +} + +- (OCMInvocationExpectation *)expectation +{ + return (OCMInvocationExpectation *)invocationMatcher; +} + + +#pragma mark Modifying the expectation + +- (id)never +{ + [[self expectation] setMatchAndReject:YES]; + return self; +} + + +#pragma mark Finishing recording + +- (void)forwardInvocation:(NSInvocation *)anInvocation +{ + [super forwardInvocation:anInvocation]; + [mockObject addExpectation:[self expectation]]; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMFunctions.h b/Pods/OCMock/Source/OCMock/OCMFunctions.h new file mode 100644 index 000000000..b0c2df353 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMFunctions.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + + +#if defined(__cplusplus) +#define OCMOCK_EXTERN extern "C" +#else +#define OCMOCK_EXTERN extern +#endif + + +OCMOCK_EXTERN BOOL OCMIsObjectType(const char *objCType); diff --git a/Pods/OCMock/Source/OCMock/OCMFunctions.m b/Pods/OCMock/Source/OCMock/OCMFunctions.m new file mode 100644 index 000000000..79f6e9aca --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMFunctions.m @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCMFunctionsPrivate.h" +#import "OCMLocation.h" +#import "OCClassMockObject.h" +#import "OCPartialMockObject.h" + + +#pragma mark Known private API + +@interface NSException(OCMKnownExceptionMethods) ++ (NSException *)failureInFile:(NSString *)file atLine:(int)line withDescription:(NSString *)formatString, ...; +@end + +@interface NSObject(OCMKnownTestCaseMethods) +- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)file atLine:(NSUInteger)line expected:(BOOL)expected; +- (void)failWithException:(NSException *)exception; +@end + + +#pragma mark Functions related to ObjC type system + +const char *OCMTypeWithoutQualifiers(const char *objCType) +{ + while(strchr("rnNoORV", objCType[0]) != NULL) + objCType += 1; + return objCType; +} + + +static BOOL OCMIsUnqualifiedClassType(const char *unqualifiedObjCType) +{ + return (strcmp(unqualifiedObjCType, @encode(Class)) == 0); +} + +BOOL OCMIsClassType(const char *objCType) +{ + return OCMIsUnqualifiedClassType(OCMTypeWithoutQualifiers(objCType)); +} + + +static BOOL OCMIsUnqualifiedBlockType(const char *unqualifiedObjCType) +{ + char blockType[] = @encode(void(^)()); + if(strcmp(unqualifiedObjCType, blockType) == 0) + return YES; + + // sometimes block argument/return types are tacked onto the type, in angle brackets + if(strncmp(unqualifiedObjCType, blockType, sizeof(blockType) - 1) == 0 && unqualifiedObjCType[sizeof(blockType) - 1] == '<') + return YES; + + return NO; +} + +BOOL OCMIsBlockType(const char *objCType) +{ + return OCMIsUnqualifiedBlockType(OCMTypeWithoutQualifiers(objCType)); +} + + +BOOL OCMIsObjectType(const char *objCType) +{ + const char *unqualifiedObjCType = OCMTypeWithoutQualifiers(objCType); + + char objectType[] = @encode(id); + if(strcmp(unqualifiedObjCType, objectType) == 0 || OCMIsUnqualifiedClassType(unqualifiedObjCType)) + return YES; + + // sometimes the name of an object's class is tacked onto the type, in double quotes + if(strncmp(unqualifiedObjCType, objectType, sizeof(objectType) - 1) == 0 && unqualifiedObjCType[sizeof(objectType) - 1] == '"') + return YES; + + // if the returnType is a typedef to an object, it has the form ^{OriginClass=#} + NSString *regexString = @"^\\^\\{(.*)=#.*\\}"; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:0 error:NULL]; + NSString *type = [NSString stringWithCString:unqualifiedObjCType encoding:NSASCIIStringEncoding]; + if([regex numberOfMatchesInString:type options:0 range:NSMakeRange(0, type.length)] > 0) + return YES; + + // if the return type is a block we treat it like an object + return OCMIsUnqualifiedBlockType(unqualifiedObjCType); +} + + +CFNumberType OCMNumberTypeForObjCType(const char *objcType) +{ + switch (objcType[0]) + { + case 'c': return kCFNumberCharType; + case 'C': return kCFNumberCharType; + case 'B': return kCFNumberCharType; + case 's': return kCFNumberShortType; + case 'S': return kCFNumberShortType; + case 'i': return kCFNumberIntType; + case 'I': return kCFNumberIntType; + case 'l': return kCFNumberLongType; + case 'L': return kCFNumberLongType; + case 'q': return kCFNumberLongLongType; + case 'Q': return kCFNumberLongLongType; + case 'f': return kCFNumberFloatType; + case 'd': return kCFNumberDoubleType; + default: return 0; + } +} + +/* + * Sometimes an external type is an opaque struct (which will have an @encode of "{structName}" + * or "{structName=}") but the actual method return type, or property type, will know the contents + * of the struct (so will have an objcType of say "{structName=iiSS}". This function will determine + * those are equal provided they have the same structure name, otherwise everything else will be + * compared textually. This can happen particularly for pointers to such structures, which still + * encode what is being pointed to. + * + * For some types some runtime functions throw exceptions, which is why we wrap this in an + * exception handler just below. + */ +static BOOL OCMEqualTypesAllowingOpaqueStructsInternal(const char *type1, const char *type2) +{ + type1 = OCMTypeWithoutQualifiers(type1); + type2 = OCMTypeWithoutQualifiers(type2); + + switch (type1[0]) + { + case '{': + case '(': + { + if (type2[0] != type1[0]) + return NO; + char endChar = type1[0] == '{'? '}' : ')'; + + const char *type1End = strchr(type1, endChar); + const char *type2End = strchr(type2, endChar); + const char *type1Equals = strchr(type1, '='); + const char *type2Equals = strchr(type2, '='); + + /* Opaque types either don't have an equals sign (just the name and the end brace), or + * empty content after the equals sign. + * We want that to compare the same as a type of the same name but with the content. + */ + BOOL type1Opaque = (type1Equals == NULL || (type1End < type1Equals) || type1Equals[1] == endChar); + BOOL type2Opaque = (type2Equals == NULL || (type2End < type2Equals) || type2Equals[1] == endChar); + const char *type1NameEnd = (type1Equals == NULL || (type1End < type1Equals)) ? type1End : type1Equals; + const char *type2NameEnd = (type2Equals == NULL || (type2End < type2Equals)) ? type2End : type2Equals; + intptr_t type1NameLen = type1NameEnd - type1; + intptr_t type2NameLen = type2NameEnd - type2; + + /* If the names are not equal, return NO */ + if (type1NameLen != type2NameLen || strncmp(type1, type2, type1NameLen)) + return NO; + + /* If the same name, and at least one is opaque, that is close enough. */ + if (type1Opaque || type2Opaque) + return YES; + + /* Otherwise, compare all the elements. Use NSGetSizeAndAlignment to walk through the struct elements. */ + type1 = type1Equals + 1; + type2 = type2Equals + 1; + while (type1[0] != endChar && type1[0] != '\0') + { + if (!OCMEqualTypesAllowingOpaqueStructs(type1, type2)) + return NO; + type1 = NSGetSizeAndAlignment(type1, NULL, NULL); + type2 = NSGetSizeAndAlignment(type2, NULL, NULL); + } + return YES; + } + case '^': + /* for a pointer, make sure the other is a pointer, then recursively compare the rest */ + if (type2[0] != type1[0]) + return NO; + return OCMEqualTypesAllowingOpaqueStructs(type1 + 1, type2 + 1); + + case '?': + return type2[0] == '?'; + + case '\0': + return type2[0] == '\0'; + + default: + { + // Move the type pointers past the current types, then compare that region + const char *afterType1 = NSGetSizeAndAlignment(type1, NULL, NULL); + const char *afterType2 = NSGetSizeAndAlignment(type2, NULL, NULL); + intptr_t type1Len = afterType1 - type1; + intptr_t type2Len = afterType2 - type2; + + return (type1Len == type2Len && (strncmp(type1, type2, type1Len) == 0)); + } + } +} + +BOOL OCMEqualTypesAllowingOpaqueStructs(const char *type1, const char *type2) +{ + @try + { + return OCMEqualTypesAllowingOpaqueStructsInternal(type1, type2); + } + @catch (NSException *e) + { + /* Probably a bitfield or something that NSGetSizeAndAlignment chokes on, oh well */ + return NO; + } +} + + +#pragma mark Creating classes + +Class OCMCreateSubclass(Class class, void *ref) +{ + const char *className = [[NSString stringWithFormat:@"%@-%p-%u", NSStringFromClass(class), ref, arc4random()] UTF8String]; + Class subclass = objc_allocateClassPair(class, className, 0); + objc_registerClassPair(subclass); + return subclass; +} + + +#pragma mark Alias for renaming real methods + +static NSString *const OCMRealMethodAliasPrefix = @"ocmock_replaced_"; +static const char *const OCMRealMethodAliasPrefixCString = "ocmock_replaced_"; + +BOOL OCMIsAliasSelector(SEL selector) +{ + return [NSStringFromSelector(selector) hasPrefix:OCMRealMethodAliasPrefix]; +} + +SEL OCMAliasForOriginalSelector(SEL selector) +{ + char aliasName[2048]; + const char *originalName = sel_getName(selector); + strlcpy(aliasName, OCMRealMethodAliasPrefixCString, sizeof(aliasName)); + strlcat(aliasName, originalName, sizeof(aliasName)); + return sel_registerName(aliasName); +} + +SEL OCMOriginalSelectorForAlias(SEL selector) +{ + if(!OCMIsAliasSelector(selector)) + [NSException raise:NSInvalidArgumentException format:@"Not an alias selector; found %@", NSStringFromSelector(selector)]; + NSString *string = NSStringFromSelector(selector); + return NSSelectorFromString([string substringFromIndex:[OCMRealMethodAliasPrefix length]]); +} + + +#pragma mark Wrappers around associative references + +static NSString *const OCMClassMethodMockObjectKey = @"OCMClassMethodMockObjectKey"; + +void OCMSetAssociatedMockForClass(OCClassMockObject *mock, Class aClass) +{ + if((mock != nil) && (objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey) != nil)) + [NSException raise:NSInternalInconsistencyException format:@"Another mock is already associated with class %@", NSStringFromClass(aClass)]; + objc_setAssociatedObject(aClass, OCMClassMethodMockObjectKey, mock, OBJC_ASSOCIATION_ASSIGN); +} + +OCClassMockObject *OCMGetAssociatedMockForClass(Class aClass, BOOL includeSuperclasses) +{ + OCClassMockObject *mock = nil; + do + { + mock = objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey); + aClass = class_getSuperclass(aClass); + } + while((mock == nil) && (aClass != nil) && includeSuperclasses); + return mock; +} + +static NSString *const OCMPartialMockObjectKey = @"OCMPartialMockObjectKey"; + +void OCMSetAssociatedMockForObject(OCClassMockObject *mock, id anObject) +{ + if((mock != nil) && (objc_getAssociatedObject(anObject, OCMPartialMockObjectKey) != nil)) + [NSException raise:NSInternalInconsistencyException format:@"Another mock is already associated with object %@", anObject]; + objc_setAssociatedObject(anObject, OCMPartialMockObjectKey, mock, OBJC_ASSOCIATION_ASSIGN); +} + +OCPartialMockObject *OCMGetAssociatedMockForObject(id anObject) +{ + return objc_getAssociatedObject(anObject, OCMPartialMockObjectKey); +} + + +#pragma mark Functions related to IDE error reporting + +void OCMReportFailure(OCMLocation *loc, NSString *description) +{ + id testCase = [loc testCase]; + if((testCase != nil) && [testCase respondsToSelector:@selector(recordFailureWithDescription:inFile:atLine:expected:)]) + { + [testCase recordFailureWithDescription:description inFile:[loc file] atLine:[loc line] expected:NO]; + } + else if((testCase != nil) && [testCase respondsToSelector:@selector(failWithException:)]) + { + NSException *exception = nil; + if([NSException instancesRespondToSelector:@selector(failureInFile:atLine:withDescription:)]) + { + exception = [NSException failureInFile:[loc file] atLine:(int)[loc line] withDescription:description]; + } + else + { + NSString *reason = [NSString stringWithFormat:@"%@:%lu %@", [loc file], (unsigned long)[loc line], description]; + exception = [NSException exceptionWithName:@"OCMockTestFailure" reason:reason userInfo:nil]; + } + [testCase failWithException:exception]; + } + else if(loc != nil) + { + NSLog(@"%@:%lu %@", [loc file], (unsigned long)[loc line], description); + NSString *reason = [NSString stringWithFormat:@"%@:%lu %@", [loc file], (unsigned long)[loc line], description]; + [[NSException exceptionWithName:@"OCMockTestFailure" reason:reason userInfo:nil] raise]; + + } + else + { + NSLog(@"%@", description); + [[NSException exceptionWithName:@"OCMockTestFailure" reason:description userInfo:nil] raise]; + } + +} diff --git a/Pods/OCMock/Source/OCMock/OCMFunctionsPrivate.h b/Pods/OCMock/Source/OCMock/OCMFunctionsPrivate.h new file mode 100644 index 000000000..1984c228c --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMFunctionsPrivate.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMLocation; +@class OCClassMockObject; +@class OCPartialMockObject; + + +BOOL OCMIsClassType(const char *objCType); +BOOL OCMIsBlockType(const char *objCType); +BOOL OCMIsObjectType(const char *objCType); +const char *OCMTypeWithoutQualifiers(const char *objCType); +BOOL OCMEqualTypesAllowingOpaqueStructs(const char *type1, const char *type2); +CFNumberType OCMNumberTypeForObjCType(const char *objcType); + +Class OCMCreateSubclass(Class cls, void *ref); + +BOOL OCMIsAliasSelector(SEL selector); +SEL OCMAliasForOriginalSelector(SEL selector); +SEL OCMOriginalSelectorForAlias(SEL selector); + +void OCMSetAssociatedMockForClass(OCClassMockObject *mock, Class aClass); +OCClassMockObject *OCMGetAssociatedMockForClass(Class aClass, BOOL includeSuperclasses); + +void OCMSetAssociatedMockForObject(OCClassMockObject *mock, id anObject); +OCPartialMockObject *OCMGetAssociatedMockForObject(id anObject); + +void OCMReportFailure(OCMLocation *loc, NSString *description); diff --git a/Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h b/Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h new file mode 100644 index 000000000..a6cd75929 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMIndirectReturnValueProvider : NSObject +{ + id provider; + SEL selector; +} + +- (id)initWithProvider:(id)aProvider andSelector:(SEL)aSelector; + +- (void)handleInvocation:(NSInvocation *)anInvocation; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.m b/Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.m new file mode 100644 index 000000000..b7c07a046 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.m @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "NSMethodSignature+OCMAdditions.h" +#import "OCMIndirectReturnValueProvider.h" +#import "NSInvocation+OCMAdditions.h" + + +@implementation OCMIndirectReturnValueProvider + +- (id)initWithProvider:(id)aProvider andSelector:(SEL)aSelector +{ + if ((self = [super init])) + { + provider = [aProvider retain]; + selector = aSelector; + } + + return self; +} + +- (void)dealloc +{ + [provider release]; + [super dealloc]; +} + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + id originalTarget = [anInvocation target]; + SEL originalSelector = [anInvocation selector]; + + [anInvocation setTarget:provider]; + [anInvocation setSelector:selector]; + [anInvocation invoke]; + + [anInvocation setTarget:originalTarget]; + [anInvocation setSelector:originalSelector]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMInvocationExpectation.h b/Pods/OCMock/Source/OCMock/OCMInvocationExpectation.h new file mode 100644 index 000000000..5a6571900 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMInvocationExpectation.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMInvocationStub.h" + +@interface OCMInvocationExpectation : OCMInvocationStub +{ + BOOL matchAndReject; + BOOL isSatisfied; +} + +- (void)setMatchAndReject:(BOOL)flag; +- (BOOL)isMatchAndReject; + +- (BOOL)isSatisfied; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMInvocationExpectation.m b/Pods/OCMock/Source/OCMock/OCMInvocationExpectation.m new file mode 100644 index 000000000..a6846d92e --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMInvocationExpectation.m @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMInvocationExpectation.h" +#import "NSInvocation+OCMAdditions.h" + + +@implementation OCMInvocationExpectation + +- (void)setMatchAndReject:(BOOL)flag +{ + matchAndReject = flag; + if(matchAndReject) + isSatisfied = YES; +} + +- (BOOL)isMatchAndReject +{ + return matchAndReject; +} + +- (BOOL)isSatisfied +{ + return isSatisfied; +} + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + [super handleInvocation:anInvocation]; + + if(matchAndReject) + { + isSatisfied = NO; + [NSException raise:NSInternalInconsistencyException format:@"%@: explicitly disallowed method invoked: %@", + [self description], [anInvocation invocationDescription]]; + } + else + { + isSatisfied = YES; + } +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMInvocationMatcher.h b/Pods/OCMock/Source/OCMock/OCMInvocationMatcher.h new file mode 100644 index 000000000..460b95af1 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMInvocationMatcher.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMInvocationMatcher : NSObject +{ + NSInvocation *recordedInvocation; + BOOL recordedAsClassMethod; + BOOL ignoreNonObjectArgs; +} + +- (void)setInvocation:(NSInvocation *)anInvocation; +- (NSInvocation *)recordedInvocation; + +- (void)setRecordedAsClassMethod:(BOOL)flag; +- (BOOL)recordedAsClassMethod; + +- (void)setIgnoreNonObjectArgs:(BOOL)flag; + +- (BOOL)matchesSelector:(SEL)aSelector; +- (BOOL)matchesInvocation:(NSInvocation *)anInvocation; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMInvocationMatcher.m b/Pods/OCMock/Source/OCMock/OCMInvocationMatcher.m new file mode 100644 index 000000000..56ed3b9e4 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMInvocationMatcher.m @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import +#import +#import "OCMPassByRefSetter.h" +#import "NSInvocation+OCMAdditions.h" +#import "OCMInvocationMatcher.h" +#import "OCClassMockObject.h" +#import "OCMFunctionsPrivate.h" +#import "OCMBlockArgCaller.h" + + +@interface NSObject(HCMatcherDummy) +- (BOOL)matches:(id)item; +@end + + +@implementation OCMInvocationMatcher + +- (void)dealloc +{ + [recordedInvocation release]; + [super dealloc]; +} + +- (void)setInvocation:(NSInvocation *)anInvocation +{ + [recordedInvocation release]; + // Don't do a regular -retainArguments on the invocation that we use for matching. NSInvocation + // effectively does an strcpy on char* arguments which messes up matching them literally and blows + // up with anyPointer (in strlen since it's not actually a C string). Also on the off-chance that + // anInvocation contains self as an argument, -retainArguments would create a retain cycle. + [anInvocation retainObjectArgumentsExcludingObject:self]; + recordedInvocation = [anInvocation retain]; +} + +- (void)setRecordedAsClassMethod:(BOOL)flag +{ + recordedAsClassMethod = flag; +} + +- (BOOL)recordedAsClassMethod +{ + return recordedAsClassMethod; +} + +- (void)setIgnoreNonObjectArgs:(BOOL)flag +{ + ignoreNonObjectArgs = flag; +} + +- (NSString *)description +{ + return [recordedInvocation invocationDescription]; +} + +- (NSInvocation *)recordedInvocation +{ + return recordedInvocation; +} + +- (BOOL)matchesSelector:(SEL)sel +{ + if(sel == [recordedInvocation selector]) + return YES; + if(OCMIsAliasSelector(sel) && + OCMOriginalSelectorForAlias(sel) == [recordedInvocation selector]) + return YES; + + return NO; +} + +- (BOOL)matchesInvocation:(NSInvocation *)anInvocation +{ + id target = [anInvocation target]; + BOOL isClassMethodInvocation = (target != nil) && (target == [target class]); + if(isClassMethodInvocation != recordedAsClassMethod) + return NO; + + if(![self matchesSelector:[anInvocation selector]]) + return NO; + + NSMethodSignature *signature = [recordedInvocation methodSignature]; + NSUInteger n = [signature numberOfArguments]; + for(NSUInteger i = 2; i < n; i++) + { + if(ignoreNonObjectArgs && !OCMIsObjectType([signature getArgumentTypeAtIndex:i])) + { + continue; + } + + id recordedArg = [recordedInvocation getArgumentAtIndexAsObject:i]; + id passedArg = [anInvocation getArgumentAtIndexAsObject:i]; + + if([recordedArg isProxy]) + { + if(![recordedArg isEqual:passedArg]) + return NO; + continue; + } + + if([recordedArg isKindOfClass:[NSValue class]]) + recordedArg = [OCMArg resolveSpecialValues:recordedArg]; + + if([recordedArg isKindOfClass:[OCMConstraint class]]) + { + if([recordedArg evaluate:passedArg] == NO) + return NO; + } + else if([recordedArg isKindOfClass:[OCMArgAction class]]) + { + // ignore, will be dealt with in handleInvocation: where applicable + } + else if([recordedArg conformsToProtocol:objc_getProtocol("HCMatcher")]) + { + if([recordedArg matches:passedArg] == NO) + return NO; + } + else + { + if(([recordedArg class] == [NSNumber class]) && + ([(NSNumber*)recordedArg compare:(NSNumber*)passedArg] != NSOrderedSame)) + return NO; + if(([recordedArg isEqual:passedArg] == NO) && + !((recordedArg == nil) && (passedArg == nil))) + return NO; + } + } + return YES; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMInvocationStub.h b/Pods/OCMock/Source/OCMock/OCMInvocationStub.h new file mode 100644 index 000000000..987f31f05 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMInvocationStub.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMInvocationMatcher.h" + +@interface OCMInvocationStub : OCMInvocationMatcher +{ + NSMutableArray *invocationActions; +} + +- (void)addInvocationAction:(id)anAction; +- (NSArray *)invocationActions; + +- (void)handleInvocation:(NSInvocation *)anInvocation; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMInvocationStub.m b/Pods/OCMock/Source/OCMock/OCMInvocationStub.m new file mode 100644 index 000000000..3c260bed0 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMInvocationStub.m @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMInvocationStub.h" +#import "OCMFunctionsPrivate.h" +#import "OCMArg.h" +#import "OCMArgAction.h" +#import "NSInvocation+OCMAdditions.h" + +@implementation OCMInvocationStub + +- (id)init +{ + self = [super init]; + invocationActions = [[NSMutableArray alloc] init]; + return self; +} + +- (void)dealloc +{ + [invocationActions release]; + [super dealloc]; +} + + +- (void)addInvocationAction:(id)anAction +{ + [invocationActions addObject:anAction]; +} + +- (NSArray *)invocationActions +{ + return invocationActions; +} + + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + NSMethodSignature *signature = [recordedInvocation methodSignature]; + NSUInteger n = [signature numberOfArguments]; + for(NSUInteger i = 2; i < n; i++) + { + id recordedArg = [recordedInvocation getArgumentAtIndexAsObject:i]; + id passedArg = [anInvocation getArgumentAtIndexAsObject:i]; + + if([recordedArg isProxy]) + continue; + + if([recordedArg isKindOfClass:[NSValue class]]) + recordedArg = [OCMArg resolveSpecialValues:recordedArg]; + + if(![recordedArg isKindOfClass:[OCMArgAction class]]) + continue; + + [recordedArg handleArgument:passedArg]; + } + + [invocationActions makeObjectsPerformSelector:@selector(handleInvocation:) withObject:anInvocation]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMLocation.h b/Pods/OCMock/Source/OCMock/OCMLocation.h new file mode 100644 index 000000000..7870c5297 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMLocation.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCMFunctions.h" + + +@interface OCMLocation : NSObject +{ + id testCase; + NSString *file; + NSUInteger line; +} + ++ (instancetype)locationWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine; + +- (instancetype)initWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine; + +- (id)testCase; +- (NSString *)file; +- (NSUInteger)line; + +@end + +OCMOCK_EXTERN OCMLocation *OCMMakeLocation(id testCase, const char *file, int line); diff --git a/Pods/OCMock/Source/OCMock/OCMLocation.m b/Pods/OCMock/Source/OCMock/OCMLocation.m new file mode 100644 index 000000000..59ca3bd19 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMLocation.m @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMLocation.h" + +@implementation OCMLocation + ++ (instancetype)locationWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine +{ + return [[[OCMLocation alloc] initWithTestCase:aTestCase file:aFile line:aLine] autorelease]; +} + +- (instancetype)initWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine +{ + if ((self = [super init])) + { + testCase = aTestCase; + file = [aFile retain]; + line = aLine; + } + + return self; +} + +- (void)dealloc +{ + [file release]; + [super dealloc]; +} + +- (id)testCase +{ + return testCase; +} + +- (NSString *)file +{ + return file; +} + +- (NSUInteger)line +{ + return line; +} + +@end + + +OCMLocation *OCMMakeLocation(id testCase, const char *fileCString, int line) +{ + return [OCMLocation locationWithTestCase:testCase file:[NSString stringWithUTF8String:fileCString] line:line]; +} + diff --git a/Pods/OCMock/Source/OCMock/OCMMacroState.h b/Pods/OCMock/Source/OCMock/OCMMacroState.h new file mode 100644 index 000000000..dba41bebd --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMMacroState.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMLocation; +@class OCMRecorder; +@class OCMStubRecorder; +@class OCMockObject; + + +@interface OCMMacroState : NSObject +{ + OCMRecorder *recorder; +} + ++ (void)beginStubMacro; ++ (OCMStubRecorder *)endStubMacro; + ++ (void)beginExpectMacro; ++ (OCMStubRecorder *)endExpectMacro; + ++ (void)beginRejectMacro; ++ (OCMStubRecorder *)endRejectMacro; + ++ (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation; ++ (void)endVerifyMacro; + ++ (OCMMacroState *)globalState; + +- (OCMRecorder *)recorder; + +- (void)switchToClassMethod; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMMacroState.m b/Pods/OCMock/Source/OCMock/OCMMacroState.m new file mode 100644 index 000000000..d50873b3e --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMMacroState.m @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMMacroState.h" +#import "OCMStubRecorder.h" +#import "OCMockObject.h" +#import "OCMExpectationRecorder.h" +#import "OCMVerifier.h" +#import "OCMInvocationMatcher.h" + + +@implementation OCMMacroState + +static NSString *const OCMGlobalStateKey = @"OCMGlobalStateKey"; + +#pragma mark Methods to begin/end macros + ++ (void)beginStubMacro +{ + OCMStubRecorder *recorder = [[[OCMStubRecorder alloc] init] autorelease]; + OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; + [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; + [macroState release]; +} + ++ (OCMStubRecorder *)endStubMacro +{ + NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary; + OCMMacroState *globalState = threadDictionary[OCMGlobalStateKey]; + OCMStubRecorder *recorder = [(OCMStubRecorder *)[globalState recorder] retain]; + [threadDictionary removeObjectForKey:OCMGlobalStateKey]; + return [recorder autorelease]; +} + + ++ (void)beginExpectMacro +{ + OCMExpectationRecorder *recorder = [[[OCMExpectationRecorder alloc] init] autorelease]; + OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; + [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; + [macroState release]; +} + ++ (OCMStubRecorder *)endExpectMacro +{ + return [self endStubMacro]; +} + + ++ (void)beginRejectMacro +{ + OCMExpectationRecorder *recorder = [[[OCMExpectationRecorder alloc] init] autorelease]; + [recorder never]; + OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; + [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; + [macroState release]; +} + ++ (OCMStubRecorder *)endRejectMacro +{ + return [self endStubMacro]; +} + + ++ (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation +{ + OCMVerifier *recorder = [[[OCMVerifier alloc] init] autorelease]; + [recorder setLocation:aLocation]; + OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; + [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; + [macroState release]; +} + ++ (void)endVerifyMacro +{ + [[NSThread currentThread].threadDictionary removeObjectForKey:OCMGlobalStateKey]; +} + + +#pragma mark Accessing global state + ++ (OCMMacroState *)globalState +{ + return [NSThread currentThread].threadDictionary[OCMGlobalStateKey]; +} + + +#pragma mark Init, dealloc, accessors + +- (id)initWithRecorder:(OCMRecorder *)aRecorder +{ + if ((self = [super init])) + { + recorder = [aRecorder retain]; + } + + return self; +} + +- (void)dealloc +{ + [recorder release]; + NSAssert([NSThread currentThread].threadDictionary[OCMGlobalStateKey] != self, @"Unexpected dealloc while set as the global state"); + [super dealloc]; +} + +- (OCMRecorder *)recorder +{ + return recorder; +} + + +#pragma mark Changing the recorder + +- (void)switchToClassMethod +{ + [recorder classMethod]; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMNotificationPoster.h b/Pods/OCMock/Source/OCMock/OCMNotificationPoster.h new file mode 100644 index 000000000..40564b4aa --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMNotificationPoster.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMNotificationPoster : NSObject +{ + NSNotification *notification; +} + +- (id)initWithNotification:(id)aNotification; + +- (void)handleInvocation:(NSInvocation *)anInvocation; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMNotificationPoster.m b/Pods/OCMock/Source/OCMock/OCMNotificationPoster.m new file mode 100644 index 000000000..0753b274c --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMNotificationPoster.m @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMNotificationPoster.h" + + +@implementation OCMNotificationPoster + +- (id)initWithNotification:(id)aNotification +{ + if ((self = [super init])) + { + notification = [aNotification retain]; + } + + return self; +} + +- (void)dealloc +{ + [notification release]; + [super dealloc]; +} + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + [[NSNotificationCenter defaultCenter] postNotification:notification]; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMObserverRecorder.h b/Pods/OCMock/Source/OCMock/OCMObserverRecorder.h new file mode 100644 index 000000000..a6ae5bfce --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMObserverRecorder.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMObserverRecorder : NSObject +{ + NSNotification *recordedNotification; +} + +- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender; + +- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender userInfo:(NSDictionary *)userInfo; + +- (BOOL)matchesNotification:(NSNotification *)aNotification; + +- (BOOL)argument:(id)expectedArg matchesArgument:(id)observedArg; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMObserverRecorder.m b/Pods/OCMock/Source/OCMock/OCMObserverRecorder.m new file mode 100644 index 000000000..921c0ec3b --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMObserverRecorder.m @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import +#import "NSInvocation+OCMAdditions.h" +#import "OCMObserverRecorder.h" + +@interface NSObject(HCMatcherDummy) +- (BOOL)matches:(id)item; +@end + +#pragma mark - + + +@implementation OCMObserverRecorder + +#pragma mark Initialisers, description, accessors, etc. + +- (void)dealloc +{ + [recordedNotification release]; + [super dealloc]; +} + + +#pragma mark Recording + +- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender +{ + recordedNotification = [[NSNotification notificationWithName:name object:sender] retain]; + return nil; +} + +- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender userInfo:(NSDictionary *)userInfo +{ + recordedNotification = [[NSNotification notificationWithName:name object:sender userInfo:userInfo] retain]; + return nil; +} + + +#pragma mark Verification + +- (BOOL)matchesNotification:(NSNotification *)aNotification +{ + return [self argument:[recordedNotification name] matchesArgument:[aNotification name]] && + [self argument:[recordedNotification object] matchesArgument:[aNotification object]] && + [self argument:[recordedNotification userInfo] matchesArgument:[aNotification userInfo]]; +} + +- (BOOL)argument:(id)expectedArg matchesArgument:(id)observedArg +{ + if([expectedArg isKindOfClass:[OCMConstraint class]]) + { + return [expectedArg evaluate:observedArg]; + } + else if([expectedArg conformsToProtocol:objc_getProtocol("HCMatcher")]) + { + return [expectedArg matches:observedArg]; + } + else if (expectedArg == observedArg) + { + return YES; + } + else if (expectedArg == nil || observedArg == nil) + { + return NO; + } + else + { + return [expectedArg isEqual:observedArg]; + } +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMPassByRefSetter.h b/Pods/OCMock/Source/OCMock/OCMPassByRefSetter.h new file mode 100644 index 000000000..bd0d5461b --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMPassByRefSetter.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMArgAction.h" + +@interface OCMPassByRefSetter : OCMArgAction +{ + id value; +} + +- (id)initWithValue:(id)value; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMPassByRefSetter.m b/Pods/OCMock/Source/OCMock/OCMPassByRefSetter.m new file mode 100644 index 000000000..271414161 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMPassByRefSetter.m @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMPassByRefSetter.h" + + +@implementation OCMPassByRefSetter + +- (id)initWithValue:(id)aValue +{ + if ((self = [super init])) + { + value = [aValue retain]; + } + + return self; +} + +- (void)dealloc +{ + [value release]; + [super dealloc]; +} + +- (void)handleArgument:(id)arg +{ + void *pointerValue = [arg pointerValue]; + if(pointerValue != NULL) + { + if([value isKindOfClass:[NSValue class]]) + [(NSValue *)value getValue:pointerValue]; + else + *(id *)pointerValue = value; + } +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.h b/Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.h new file mode 100644 index 000000000..92485f132 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2010-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMRealObjectForwarder : NSObject +{ +} + +- (void)handleInvocation:(NSInvocation *)anInvocation; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.m b/Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.m new file mode 100644 index 000000000..c081a279f --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.m @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCPartialMockObject.h" +#import "OCMRealObjectForwarder.h" +#import "OCMFunctionsPrivate.h" + + +@implementation OCMRealObjectForwarder + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + id invocationTarget = [anInvocation target]; + + [anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation selector])]; + if ([invocationTarget isProxy]) + { + if (class_getInstanceMethod([invocationTarget mockObjectClass], @selector(realObject))) + { + // the method has been invoked on the mock, we need to change the target to the real object + [anInvocation setTarget:[(OCPartialMockObject *)invocationTarget realObject]]; + } + else + { + [NSException raise:NSInternalInconsistencyException + format:@"Method andForwardToRealObject can only be used with partial mocks and class methods."]; + } + } + + [anInvocation invoke]; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMRecorder.h b/Pods/OCMock/Source/OCMock/OCMRecorder.h new file mode 100644 index 000000000..9670d085f --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMRecorder.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMockObject; +@class OCMInvocationMatcher; + + +@interface OCMRecorder : NSProxy +{ + OCMockObject *mockObject; + OCMInvocationMatcher *invocationMatcher; +} + +- (instancetype)init; +- (instancetype)initWithMockObject:(OCMockObject *)aMockObject; + +- (void)setMockObject:(OCMockObject *)aMockObject; + +- (OCMInvocationMatcher *)invocationMatcher; + +- (id)classMethod; +- (id)ignoringNonObjectArgs; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMRecorder.m b/Pods/OCMock/Source/OCMock/OCMRecorder.m new file mode 100644 index 000000000..273563adc --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMRecorder.m @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCMRecorder.h" +#import "OCMockObject.h" +#import "OCMInvocationMatcher.h" +#import "OCClassMockObject.h" + +@implementation OCMRecorder + +- (instancetype)init +{ + // no super, we're inheriting from NSProxy + return self; +} + +- (instancetype)initWithMockObject:(OCMockObject *)aMockObject +{ + [self init]; + [self setMockObject:aMockObject]; + return self; +} + +- (void)setMockObject:(OCMockObject *)aMockObject +{ + mockObject = aMockObject; +} + +- (void)dealloc +{ + [invocationMatcher release]; + [super dealloc]; +} + +- (NSString *)description +{ + return [invocationMatcher description]; +} + +- (OCMInvocationMatcher *)invocationMatcher +{ + return invocationMatcher; +} + + +#pragma mark Modifying the matcher + +- (id)classMethod +{ + // should we handle the case where this is called with a mock that isn't a class mock? + [invocationMatcher setRecordedAsClassMethod:YES]; + return self; +} + +- (id)ignoringNonObjectArgs +{ + [invocationMatcher setIgnoreNonObjectArgs:YES]; + return self; +} + + +#pragma mark Recording the actual invocation + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + if([invocationMatcher recordedAsClassMethod]) + return [[(OCClassMockObject *)mockObject mockedClass] methodSignatureForSelector:aSelector]; + + NSMethodSignature *signature = [mockObject methodSignatureForSelector:aSelector]; + if(signature == nil) + { + // if we're a working with a class mock and there is a class method, auto-switch + if(([object_getClass(mockObject) isSubclassOfClass:[OCClassMockObject class]]) && + ([[(OCClassMockObject *)mockObject mockedClass] respondsToSelector:aSelector])) + { + [self classMethod]; + signature = [self methodSignatureForSelector:aSelector]; + } + } + return signature; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation +{ + [anInvocation setTarget:nil]; + [invocationMatcher setInvocation:anInvocation]; +} + +- (void)doesNotRecognizeSelector:(SEL)aSelector +{ + [NSException raise:NSInvalidArgumentException format:@"%@: cannot stub/expect/verify method '%@' because no such method exists in the mocked class.", mockObject, NSStringFromSelector(aSelector)]; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMReturnValueProvider.h b/Pods/OCMock/Source/OCMock/OCMReturnValueProvider.h new file mode 100644 index 000000000..673b40faf --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMReturnValueProvider.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMReturnValueProvider : NSObject +{ + id returnValue; +} + +- (instancetype)initWithValue:(id)aValue; + +- (void)handleInvocation:(NSInvocation *)anInvocation; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMReturnValueProvider.m b/Pods/OCMock/Source/OCMock/OCMReturnValueProvider.m new file mode 100644 index 000000000..ed8dfec24 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMReturnValueProvider.m @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "NSMethodSignature+OCMAdditions.h" +#import "OCMReturnValueProvider.h" +#import "OCMFunctions.h" + + +@implementation OCMReturnValueProvider + +- (instancetype)initWithValue:(id)aValue +{ + if ((self = [super init])) + { + returnValue = [aValue retain]; + } + + return self; +} + +- (void)dealloc +{ + [returnValue release]; + [super dealloc]; +} + +- (void)handleInvocation:(NSInvocation *)anInvocation +{ + if(!OCMIsObjectType([[anInvocation methodSignature] methodReturnType])) + { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Expected invocation with object return type. Did you mean to use andReturnValue: instead?" userInfo:nil]; + } + NSString *sel = NSStringFromSelector([anInvocation selector]); + if([sel hasPrefix:@"alloc"] || [sel hasPrefix:@"new"] || [sel hasPrefix:@"copy"] || [sel hasPrefix:@"mutableCopy"]) + { + // methods that "create" an object return it with an extra retain count + [returnValue retain]; + } + [anInvocation setReturnValue:&returnValue]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMStubRecorder.h b/Pods/OCMock/Source/OCMock/OCMStubRecorder.h new file mode 100644 index 000000000..e32029fc2 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMStubRecorder.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2004-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import +#import + +@interface OCMStubRecorder : OCMRecorder + +- (id)andReturn:(id)anObject; +- (id)andReturnValue:(NSValue *)aValue; +- (id)andThrow:(NSException *)anException; +- (id)andPost:(NSNotification *)aNotification; +- (id)andCall:(SEL)selector onObject:(id)anObject; +- (id)andDo:(void (^)(NSInvocation *invocation))block; +- (id)andForwardToRealObject; + +@end + + +@interface OCMStubRecorder (Properties) + +#define andReturn(aValue) _andReturn(({ \ + __typeof__(aValue) _val = (aValue); \ + NSValue *_nsval = [NSValue value:&_val withObjCType:@encode(__typeof__(_val))]; \ + if (OCMIsObjectType(@encode(__typeof(_val)))) { \ + objc_setAssociatedObject(_nsval, "OCMAssociatedBoxedValue", *(__unsafe_unretained id *) (void *) &_val, OBJC_ASSOCIATION_RETAIN); \ + } \ + _nsval; \ +})) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSValue *); + +#define andThrow(anException) _andThrow(anException) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andThrow)(NSException *); + +#define andPost(aNotification) _andPost(aNotification) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andPost)(NSNotification *); + +#define andCall(anObject, aSelector) _andCall(anObject, aSelector) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andCall)(id, SEL); + +#define andDo(aBlock) _andDo(aBlock) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andDo)(void (^)(NSInvocation *)); + +#define andForwardToRealObject() _andForwardToRealObject() +@property (nonatomic, readonly) OCMStubRecorder *(^ _andForwardToRealObject)(void); + +@end + + + diff --git a/Pods/OCMock/Source/OCMock/OCMStubRecorder.m b/Pods/OCMock/Source/OCMock/OCMStubRecorder.m new file mode 100644 index 000000000..537f6e3b6 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMStubRecorder.m @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2004-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMStubRecorder.h" +#import "OCClassMockObject.h" +#import "OCMReturnValueProvider.h" +#import "OCMBoxedReturnValueProvider.h" +#import "OCMExceptionReturnValueProvider.h" +#import "OCMIndirectReturnValueProvider.h" +#import "OCMNotificationPoster.h" +#import "OCMBlockCaller.h" +#import "OCMRealObjectForwarder.h" +#import "OCMFunctions.h" +#import "OCMInvocationStub.h" + + +@implementation OCMStubRecorder + +#pragma mark Initialisers, description, accessors, etc. + +- (id)init +{ + if(invocationMatcher != nil) + [NSException raise:NSInternalInconsistencyException format:@"** Method init invoked twice on stub recorder. Are you trying to mock the init method? This is currently not supported."]; + + self = [super init]; + invocationMatcher = [[OCMInvocationStub alloc] init]; + return self; +} + +- (OCMInvocationStub *)stub +{ + return (OCMInvocationStub *)invocationMatcher; +} + + +#pragma mark Recording invocation actions + +- (id)andReturn:(id)anObject +{ + [[self stub] addInvocationAction:[[[OCMReturnValueProvider alloc] initWithValue:anObject] autorelease]]; + return self; +} + +- (id)andReturnValue:(NSValue *)aValue +{ + [[self stub] addInvocationAction:[[[OCMBoxedReturnValueProvider alloc] initWithValue:aValue] autorelease]]; + return self; +} + +- (id)andThrow:(NSException *)anException +{ + [[self stub] addInvocationAction:[[[OCMExceptionReturnValueProvider alloc] initWithValue:anException] autorelease]]; + return self; +} + +- (id)andPost:(NSNotification *)aNotification +{ + [[self stub] addInvocationAction:[[[OCMNotificationPoster alloc] initWithNotification:aNotification] autorelease]]; + return self; +} + +- (id)andCall:(SEL)selector onObject:(id)anObject +{ + [[self stub] addInvocationAction:[[[OCMIndirectReturnValueProvider alloc] initWithProvider:anObject andSelector:selector] autorelease]]; + return self; +} + +- (id)andDo:(void (^)(NSInvocation *))aBlock +{ + [[self stub] addInvocationAction:[[[OCMBlockCaller alloc] initWithCallBlock:aBlock] autorelease]]; + return self; +} + +- (id)andForwardToRealObject +{ + [[self stub] addInvocationAction:[[[OCMRealObjectForwarder alloc] init] autorelease]]; + return self; +} + + +#pragma mark Finishing recording + +- (void)forwardInvocation:(NSInvocation *)anInvocation +{ + [super forwardInvocation:anInvocation]; + [mockObject addStub:[self stub]]; +} + + +@end + + +@implementation OCMStubRecorder (Properties) + +@dynamic _andReturn; + +- (OCMStubRecorder *(^)(NSValue *))_andReturn +{ + id (^theBlock)(id) = ^ (NSValue *aValue) + { + if(OCMIsObjectType([aValue objCType])) + { + NSValue *objValue = nil; + [aValue getValue:&objValue]; + return [self andReturn:objValue]; + } + else + { + return [self andReturnValue:aValue]; + } + }; + return [[theBlock copy] autorelease]; +} + + +@dynamic _andThrow; + +- (OCMStubRecorder *(^)(NSException *))_andThrow +{ + id (^theBlock)(id) = ^ (NSException * anException) + { + return [self andThrow:anException]; + }; + return [[theBlock copy] autorelease]; +} + + +@dynamic _andPost; + +- (OCMStubRecorder *(^)(NSNotification *))_andPost +{ + id (^theBlock)(id) = ^ (NSNotification * aNotification) + { + return [self andPost:aNotification]; + }; + return [[theBlock copy] autorelease]; +} + + +@dynamic _andCall; + +- (OCMStubRecorder *(^)(id, SEL))_andCall +{ + id (^theBlock)(id, SEL) = ^ (id anObject, SEL aSelector) + { + return [self andCall:aSelector onObject:anObject]; + }; + return [[theBlock copy] autorelease]; +} + + +@dynamic _andDo; + +- (OCMStubRecorder *(^)(void (^)(NSInvocation *)))_andDo +{ + id (^theBlock)(void (^)(NSInvocation *)) = ^ (void (^ blockToCall)(NSInvocation *)) + { + return [self andDo:blockToCall]; + }; + return [[theBlock copy] autorelease]; +} + + +@dynamic _andForwardToRealObject; + +- (OCMStubRecorder *(^)(void))_andForwardToRealObject +{ + id (^theBlock)(void) = ^ (void) + { + return [self andForwardToRealObject]; + }; + return [[theBlock copy] autorelease]; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMVerifier.h b/Pods/OCMock/Source/OCMock/OCMVerifier.h new file mode 100644 index 000000000..3fda12e4c --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMVerifier.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMRecorder.h" +#import "OCMLocation.h" + + +@interface OCMVerifier : OCMRecorder + +@property(retain) OCMLocation *location; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMVerifier.m b/Pods/OCMock/Source/OCMock/OCMVerifier.m new file mode 100644 index 000000000..0d07a766f --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMVerifier.m @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCMVerifier.h" +#import "OCMockObject.h" +#import "OCMLocation.h" +#import "OCMInvocationMatcher.h" + + +@implementation OCMVerifier + +- (id)init +{ + if ((self = [super init])) + { + invocationMatcher = [[OCMInvocationMatcher alloc] init]; + } + + return self; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation +{ + [super forwardInvocation:anInvocation]; + [mockObject verifyInvocation:invocationMatcher atLocation:self.location]; +} + +- (void)dealloc +{ + [_location release]; + [super dealloc]; +} + +@end diff --git a/Pods/OCMock/Source/OCMock/OCMock.h b/Pods/OCMock/Source/OCMock/OCMock.h new file mode 100644 index 000000000..9d558135b --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMock.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2004-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +#define OCMClassMock(cls) [OCMockObject niceMockForClass:cls] + +#define OCMStrictClassMock(cls) [OCMockObject mockForClass:cls] + +#define OCMProtocolMock(protocol) [OCMockObject niceMockForProtocol:protocol] + +#define OCMStrictProtocolMock(protocol) [OCMockObject mockForProtocol:protocol] + +#define OCMPartialMock(obj) [OCMockObject partialMockForObject:obj] + +#define OCMObserverMock() [OCMockObject observerMock] + + +#define OCMStub(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginStubMacro]; \ + OCMStubRecorder *recorder = nil; \ + @try{ \ + invocation; \ + }@finally{ \ + recorder = [OCMMacroState endStubMacro]; \ + } \ + recorder; \ + ); \ +}) + +#define OCMExpect(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginExpectMacro]; \ + OCMStubRecorder *recorder = nil; \ + @try{ \ + invocation; \ + }@finally{ \ + recorder = [OCMMacroState endExpectMacro]; \ + } \ + recorder; \ + ); \ +}) + +#define OCMReject(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginRejectMacro]; \ + OCMStubRecorder *recorder = nil; \ + @try{ \ + invocation; \ + }@finally{ \ + recorder = [OCMMacroState endRejectMacro]; \ + } \ + recorder; \ + ); \ +}) + +#define ClassMethod(invocation) \ + _OCMSilenceWarnings( \ + [[OCMMacroState globalState] switchToClassMethod]; \ + invocation; \ + ); + + +#define OCMVerifyAll(mock) [mock verifyAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)] + +#define OCMVerifyAllWithDelay(mock, delay) [mock verifyWithDelay:delay atLocation:OCMMakeLocation(self, __FILE__, __LINE__)] + +#define OCMVerify(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \ + @try{ \ + invocation; \ + }@finally{ \ + [OCMMacroState endVerifyMacro]; \ + } \ + ); \ +}) + +#define _OCMSilenceWarnings(macro) \ +({ \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunused-value\"") \ + _Pragma("clang diagnostic ignored \"-Wunused-getter-return-value\"") \ + macro \ + _Pragma("clang diagnostic pop") \ +}) diff --git a/Pods/OCMock/Source/OCMock/OCMockObject.h b/Pods/OCMock/Source/OCMock/OCMockObject.h new file mode 100644 index 000000000..31f7ac41d --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMockObject.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2004-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMLocation; +@class OCMInvocationStub; +@class OCMStubRecorder; +@class OCMInvocationMatcher; +@class OCMInvocationExpectation; + + +@interface OCMockObject : NSProxy +{ + BOOL isNice; + BOOL expectationOrderMatters; + NSMutableArray *stubs; + NSMutableArray *expectations; + NSMutableArray *exceptions; + NSMutableArray *invocations; +} + ++ (id)mockForClass:(Class)aClass; ++ (id)mockForProtocol:(Protocol *)aProtocol; ++ (id)partialMockForObject:(NSObject *)anObject; + ++ (id)niceMockForClass:(Class)aClass; ++ (id)niceMockForProtocol:(Protocol *)aProtocol; + ++ (id)observerMock; + +- (instancetype)init; + +- (void)setExpectationOrderMatters:(BOOL)flag; + +- (id)stub; +- (id)expect; +- (id)reject; + +- (id)verify; +- (id)verifyAtLocation:(OCMLocation *)location; + +- (void)verifyWithDelay:(NSTimeInterval)delay; +- (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location; + +- (void)stopMocking; + +// internal use only + +- (void)addStub:(OCMInvocationStub *)aStub; +- (void)addExpectation:(OCMInvocationExpectation *)anExpectation; + +- (BOOL)handleInvocation:(NSInvocation *)anInvocation; +- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation; +- (BOOL)handleSelector:(SEL)sel; + +- (void)verifyInvocation:(OCMInvocationMatcher *)matcher; +- (void)verifyInvocation:(OCMInvocationMatcher *)matcher atLocation:(OCMLocation *)location; + +@end + diff --git a/Pods/OCMock/Source/OCMock/OCMockObject.m b/Pods/OCMock/Source/OCMock/OCMockObject.m new file mode 100644 index 000000000..0f79f119c --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCMockObject.m @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2004-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCClassMockObject.h" +#import "OCProtocolMockObject.h" +#import "OCPartialMockObject.h" +#import "OCObserverMockObject.h" +#import "OCMStubRecorder.h" +#import +#import "NSInvocation+OCMAdditions.h" +#import "OCMInvocationMatcher.h" +#import "OCMMacroState.h" +#import "OCMFunctionsPrivate.h" +#import "OCMVerifier.h" +#import "OCMInvocationExpectation.h" +#import "OCMExceptionReturnValueProvider.h" +#import "OCMExpectationRecorder.h" + + +@implementation OCMockObject + +#pragma mark Class initialisation + ++ (void)initialize +{ + if([[NSInvocation class] instanceMethodSignatureForSelector:@selector(getArgumentAtIndexAsObject:)] == NULL) + [NSException raise:NSInternalInconsistencyException format:@"** Expected method not present; the method getArgumentAtIndexAsObject: is not implemented by NSInvocation. If you see this exception it is likely that you are using the static library version of OCMock and your project is not configured correctly to load categories from static libraries. Did you forget to add the -ObjC linker flag?"]; +} + + +#pragma mark Factory methods + ++ (id)mockForClass:(Class)aClass +{ + return [[[OCClassMockObject alloc] initWithClass:aClass] autorelease]; +} + ++ (id)mockForProtocol:(Protocol *)aProtocol +{ + return [[[OCProtocolMockObject alloc] initWithProtocol:aProtocol] autorelease]; +} + ++ (id)partialMockForObject:(NSObject *)anObject +{ + return [[[OCPartialMockObject alloc] initWithObject:anObject] autorelease]; +} + + ++ (id)niceMockForClass:(Class)aClass +{ + return [self _makeNice:[self mockForClass:aClass]]; +} + ++ (id)niceMockForProtocol:(Protocol *)aProtocol +{ + return [self _makeNice:[self mockForProtocol:aProtocol]]; +} + + ++ (id)_makeNice:(OCMockObject *)mock +{ + mock->isNice = YES; + return mock; +} + + ++ (id)observerMock +{ + return [[[OCObserverMockObject alloc] init] autorelease]; +} + + +#pragma mark Initialisers, description, accessors, etc. + +- (instancetype)init +{ + // check if we are called from inside a macro + OCMRecorder *recorder = [[OCMMacroState globalState] recorder]; + if(recorder != nil) + { + [recorder setMockObject:self]; + return (id)[recorder init]; + } + + // no [super init], we're inheriting from NSProxy + expectationOrderMatters = NO; + stubs = [[NSMutableArray alloc] init]; + expectations = [[NSMutableArray alloc] init]; + exceptions = [[NSMutableArray alloc] init]; + invocations = [[NSMutableArray alloc] init]; + return self; +} + +- (void)dealloc +{ + [stubs release]; + [expectations release]; + [exceptions release]; + [invocations release]; + [super dealloc]; +} + +- (NSString *)description +{ + return @"OCMockObject"; +} + +- (void)addStub:(OCMInvocationStub *)aStub +{ + @synchronized(stubs) + { + [stubs addObject:aStub]; + } +} + +- (void)addExpectation:(OCMInvocationExpectation *)anExpectation +{ + @synchronized(expectations) + { + [expectations addObject:anExpectation]; + } +} + + +#pragma mark Public API + +- (void)setExpectationOrderMatters:(BOOL)flag +{ + expectationOrderMatters = flag; +} + +- (void)stopMocking +{ + // no-op for mock objects that are not class object or partial mocks +} + + +- (id)stub +{ + return [[[OCMStubRecorder alloc] initWithMockObject:self] autorelease]; +} + +- (id)expect +{ + return [[[OCMExpectationRecorder alloc] initWithMockObject:self] autorelease]; +} + +- (id)reject +{ + return [[self expect] never]; +} + + +- (id)verify +{ + return [self verifyAtLocation:nil]; +} + +- (id)verifyAtLocation:(OCMLocation *)location +{ + NSMutableArray *unsatisfiedExpectations = [NSMutableArray array]; + @synchronized(expectations) + { + for(OCMInvocationExpectation *e in expectations) + { + if(![e isSatisfied]) + [unsatisfiedExpectations addObject:e]; + } + } + + if([unsatisfiedExpectations count] == 1) + { + NSString *description = [NSString stringWithFormat:@"%@: expected method was not invoked: %@", + [self description], [[unsatisfiedExpectations objectAtIndex:0] description]]; + OCMReportFailure(location, description); + } + else if([unsatisfiedExpectations count] > 0) + { + NSString *description = [NSString stringWithFormat:@"%@: %@ expected methods were not invoked: %@", + [self description], @([unsatisfiedExpectations count]), [self _stubDescriptions:YES]]; + OCMReportFailure(location, description); + } + + OCMInvocationExpectation *firstException = nil; + @synchronized(exceptions) + { + firstException = [exceptions.firstObject retain]; + } + if(firstException) + { + NSString *description = [NSString stringWithFormat:@"%@: %@ (This is a strict mock failure that was ignored when it actually occurred.)", + [self description], [firstException description]]; + OCMReportFailure(location, description); + } + [firstException release]; + + return [[[OCMVerifier alloc] initWithMockObject:self] autorelease]; +} + + +- (void)verifyWithDelay:(NSTimeInterval)delay +{ + [self verifyWithDelay:delay atLocation:nil]; +} + +- (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location +{ + NSTimeInterval step = 0.01; + while(delay > 0) + { + @synchronized(expectations) + { + if([expectations count] == 0) + break; + } + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:MIN(step, delay)]]; + delay -= step; + step *= 2; + } + [self verifyAtLocation:location]; +} + + +#pragma mark Verify after running + +- (void)verifyInvocation:(OCMInvocationMatcher *)matcher +{ + [self verifyInvocation:matcher atLocation:nil]; +} + +- (void)verifyInvocation:(OCMInvocationMatcher *)matcher atLocation:(OCMLocation *)location +{ + @synchronized(invocations) + { + for(NSInvocation *invocation in invocations) + { + if([matcher matchesInvocation:invocation]) + return; + } + } + NSString *description = [NSString stringWithFormat:@"%@: Method %@ was not invoked.", + [self description], [matcher description]]; + + OCMReportFailure(location, description); +} + + +#pragma mark Handling invocations + +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + if([OCMMacroState globalState] != nil) + { + OCMRecorder *recorder = [[OCMMacroState globalState] recorder]; + [recorder setMockObject:self]; + return recorder; + } + return nil; +} + + +- (BOOL)handleSelector:(SEL)sel +{ + @synchronized(stubs) + { + for(OCMInvocationStub *recorder in stubs) + if([recorder matchesSelector:sel]) + return YES; + } + return NO; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation +{ + @try + { + if([self handleInvocation:anInvocation] == NO) + [self handleUnRecordedInvocation:anInvocation]; + } + @catch(NSException *e) + { + if([[e name] isEqualToString:OCMStubbedException]) + { + e = [[e userInfo] objectForKey:@"exception"]; + } + else + { + // add non-stubbed method to list of exceptions to be re-raised in verify + @synchronized(exceptions) + { + [exceptions addObject:e]; + } + } + [e raise]; + } +} + +- (BOOL)handleInvocation:(NSInvocation *)anInvocation +{ + @synchronized(invocations) + { + // We can't do a normal retain arguments on anInvocation because its target/arguments/return + // value could be self. That would produce a retain cycle self->invocations->anInvocation->self. + // However we need to retain everything on anInvocation that isn't self because we expect them to + // stick around after this method returns. Use our special method to retain just what's needed. + [anInvocation retainObjectArgumentsExcludingObject:self]; + [invocations addObject:anInvocation]; + } + + OCMInvocationStub *stub = nil; + @synchronized(stubs) + { + for(stub in stubs) + { + // If the stub forwards its invocation to the real object, then we don't want to do handleInvocation: yet, since forwarding the invocation to the real object could call a method that is expected to happen after this one, which is bad if expectationOrderMatters is YES + if([stub matchesInvocation:anInvocation]) + break; + } + // Retain the stub in case it ends up being removed from stubs and expectations, since we still have to call handleInvocation on the stub at the end + [stub retain]; + } + if(stub == nil) + return NO; + + BOOL removeStub = NO; + @synchronized(expectations) + { + if([expectations containsObject:stub]) + { + OCMInvocationExpectation *expectation = [self _nextExpectedInvocation]; + if(expectationOrderMatters && (expectation != stub)) + { + [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@\n\texpected:\t%@", + [self description], [stub description], [[expectations objectAtIndex:0] description]]; + } + + // We can't check isSatisfied yet, since the stub won't be satisfied until we call handleInvocation:, and we don't want to call handleInvocation: yes for the reason in the comment above, since we'll still have the current expectation in the expectations array, which will cause an exception if expectationOrderMatters is YES and we're not ready for any future expected methods to be called yet + if(![(OCMInvocationExpectation *)stub isMatchAndReject]) + { + [expectations removeObject:stub]; + removeStub = YES; + } + } + } + if(removeStub) + { + @synchronized(stubs) + { + [stubs removeObject:stub]; + } + } + [stub handleInvocation:anInvocation]; + [stub release]; + + return YES; +} + +// Must be synchronized on expectations when calling this method. +- (OCMInvocationExpectation *)_nextExpectedInvocation +{ + for(OCMInvocationExpectation *expectation in expectations) + if(![expectation isMatchAndReject]) + return expectation; + return nil; +} + +- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation +{ + if(isNice == NO) + { + [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@ %@", + [self description], [anInvocation invocationDescription], [self _stubDescriptions:NO]]; + } +} + +- (void)doesNotRecognizeSelector:(SEL)aSelector __unused +{ + if([OCMMacroState globalState] != nil) + { + // we can't do anything clever with the macro state because we must raise an exception here + [NSException raise:NSInvalidArgumentException format:@"%@: Cannot stub/expect/verify method '%@' because no such method exists in the mocked class.", + [self description], NSStringFromSelector(aSelector)]; + } + else + { + [NSException raise:NSInvalidArgumentException format:@"-[%@ %@]: unrecognized selector sent to instance %p", + [self description], NSStringFromSelector(aSelector), (void *)self]; + } +} + + +#pragma mark Helper methods + +- (NSString *)_stubDescriptions:(BOOL)onlyExpectations +{ + NSMutableString *outputString = [NSMutableString string]; + NSArray *stubsCopy = nil; + @synchronized(stubs) + { + stubsCopy = [stubs copy]; + } + for(OCMStubRecorder *stub in stubsCopy) + { + BOOL expectationsContainStub = NO; + @synchronized(expectations) + { + expectationsContainStub = [expectations containsObject:stub]; + } + + NSString *prefix = @""; + + if(onlyExpectations) + { + if(expectationsContainStub == NO) + continue; + } + else + { + if(expectationsContainStub) + prefix = @"expected:\t"; + else + prefix = @"stubbed:\t"; + } + [outputString appendFormat:@"\n\t%@%@", prefix, [stub description]]; + } + [stubsCopy release]; + return outputString; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCObserverMockObject.h b/Pods/OCMock/Source/OCMock/OCObserverMockObject.h new file mode 100644 index 000000000..3cbcd3c0d --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCObserverMockObject.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMLocation; + + +@interface OCObserverMockObject : NSObject +{ + BOOL expectationOrderMatters; + NSMutableArray *recorders; + NSMutableArray *centers; +} + +- (void)setExpectationOrderMatters:(BOOL)flag; + +- (id)expect; + +- (void)verify; +- (void)verifyAtLocation:(OCMLocation *)location; + +- (void)handleNotification:(NSNotification *)aNotification; + +// internal use + +- (void)autoRemoveFromCenter:(NSNotificationCenter *)aCenter; +- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCObserverMockObject.m b/Pods/OCMock/Source/OCMock/OCObserverMockObject.m new file mode 100644 index 000000000..03db0d346 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCObserverMockObject.m @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCObserverMockObject.h" +#import "OCMObserverRecorder.h" +#import "OCMLocation.h" +#import "OCMFunctionsPrivate.h" + + +@implementation OCObserverMockObject + +#pragma mark Initialisers, description, accessors, etc. + +- (id)init +{ + if ((self = [super init])) + { + recorders = [[NSMutableArray alloc] init]; + centers = [[NSMutableArray alloc] init]; + } + + return self; +} + +- (id)retain +{ + return [super retain]; +} + +- (void)dealloc +{ + for(NSNotificationCenter *c in centers) + [c removeObserver:self]; + [centers release]; + [recorders release]; + [super dealloc]; +} + +- (NSString *)description +{ + return @"OCMockObserver"; +} + +- (void)setExpectationOrderMatters:(BOOL)flag +{ + expectationOrderMatters = flag; +} + +- (void)autoRemoveFromCenter:(NSNotificationCenter *)aCenter +{ + @synchronized(centers) + { + [centers addObject:aCenter]; + } +} + + +#pragma mark Public API + +- (id)expect +{ + OCMObserverRecorder *recorder = [[[OCMObserverRecorder alloc] init] autorelease]; + @synchronized(recorders) + { + [recorders addObject:recorder]; + } + return recorder; +} + +- (void)verify +{ + [self verifyAtLocation:nil]; +} + +- (void)verifyAtLocation:(OCMLocation *)location +{ + @synchronized(recorders) + { + if([recorders count] == 1) + { + NSString *description = [NSString stringWithFormat:@"%@: expected notification was not observed: %@", + [self description], [[recorders lastObject] description]]; + OCMReportFailure(location, description); + } + else if([recorders count] > 0) + { + NSString *description = [NSString stringWithFormat:@"%@ : %@ expected notifications were not observed.", + [self description], @([recorders count])]; + OCMReportFailure(location, description); + } + } +} + + +#pragma mark Receiving recording requests via macro + +- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender +{ + return [[self expect] notificationWithName:name object:sender]; +} + + +#pragma mark Receiving notifications + +- (void)handleNotification:(NSNotification *)aNotification +{ + @synchronized(recorders) + { + NSUInteger i, limit; + + limit = expectationOrderMatters ? 1 : [recorders count]; + for(i = 0; i < limit; i++) + { + if([[recorders objectAtIndex:i] matchesNotification:aNotification]) + { + [recorders removeObjectAtIndex:i]; + return; + } + } + } + [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected notification observed: %@", [self description], + [aNotification description]]; +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCPartialMockObject.h b/Pods/OCMock/Source/OCMock/OCPartialMockObject.h new file mode 100644 index 000000000..5a8fd0eae --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCPartialMockObject.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCClassMockObject.h" + +@interface OCPartialMockObject : OCClassMockObject +{ + NSObject *realObject; +} + +- (id)initWithObject:(NSObject *)anObject; + +- (NSObject *)realObject; + +@end diff --git a/Pods/OCMock/Source/OCMock/OCPartialMockObject.m b/Pods/OCMock/Source/OCMock/OCPartialMockObject.m new file mode 100644 index 000000000..991e994fc --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCPartialMockObject.m @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2009-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCMockObject.h" +#import "OCPartialMockObject.h" +#import "NSMethodSignature+OCMAdditions.h" +#import "NSObject+OCMAdditions.h" +#import "OCMFunctionsPrivate.h" +#import "OCMInvocationStub.h" + + +@implementation OCPartialMockObject + +#pragma mark Initialisers, description, accessors, etc. + +- (id)initWithObject:(NSObject *)anObject +{ + NSParameterAssert(anObject != nil); + [self assertClassIsSupported:[anObject class]]; + [super initWithClass:[anObject class]]; + realObject = [anObject retain]; + [self prepareObjectForInstanceMethodMocking]; + return self; +} + +- (void)dealloc +{ + [self stopMocking]; + [realObject release]; + [super dealloc]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(mockedClass)]; +} + +- (NSObject *)realObject +{ + return realObject; +} + +#pragma mark Helper methods + +- (void)assertClassIsSupported:(Class)class +{ + NSString *classname = NSStringFromClass(class); + NSString *reason = nil; + if([classname hasPrefix:@"__NSTagged"] || [classname hasPrefix:@"NSTagged"]) + reason = [NSString stringWithFormat:@"OCMock does not support partially mocking tagged classes; got %@", classname]; + else if([classname hasPrefix:@"__NSCF"]) + reason = [NSString stringWithFormat:@"OCMock does not support partially mocking toll-free bridged classes; got %@", classname]; + + if(reason != nil) + [[NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil] raise]; +} + + +#pragma mark Extending/overriding superclass behaviour + +- (void)stopMocking +{ + if(realObject != nil) + { + Class partialMockClass = object_getClass(realObject); + OCMSetAssociatedMockForObject(nil, realObject); + object_setClass(realObject, [self mockedClass]); + [realObject release]; + realObject = nil; + objc_disposeClassPair(partialMockClass); + } + [super stopMocking]; +} + +- (void)addStub:(OCMInvocationStub *)aStub +{ + [super addStub:aStub]; + if(![aStub recordedAsClassMethod]) + [self setupForwarderForSelector:[[aStub recordedInvocation] selector]]; +} + +- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation +{ + [anInvocation invokeWithTarget:realObject]; +} + + +#pragma mark Subclass management + +- (void)prepareObjectForInstanceMethodMocking +{ + OCMSetAssociatedMockForObject(self, realObject); + + /* dynamically create a subclass and set it as the class of the object */ + Class subclass = OCMCreateSubclass(mockedClass, realObject); + object_setClass(realObject, subclass); + + /* point forwardInvocation: of the object to the implementation in the mock */ + Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForRealObject:)); + IMP myForwardIMP = method_getImplementation(myForwardMethod); + class_addMethod(subclass, @selector(forwardInvocation:), myForwardIMP, method_getTypeEncoding(myForwardMethod)); + + /* do the same for forwardingTargetForSelector, remember existing imp with alias selector */ + Method myForwardingTargetMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardingTargetForSelectorForRealObject:)); + IMP myForwardingTargetIMP = method_getImplementation(myForwardingTargetMethod); + IMP originalForwardingTargetIMP = [mockedClass instanceMethodForSelector:@selector(forwardingTargetForSelector:)]; + class_addMethod(subclass, @selector(forwardingTargetForSelector:), myForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod)); + class_addMethod(subclass, @selector(ocmock_replaced_forwardingTargetForSelector:), originalForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod)); + + /* We also override the -class method to return the original class */ + Method myObjectClassMethod = class_getInstanceMethod([self mockObjectClass], @selector(classForRealObject)); + const char *objectClassTypes = method_getTypeEncoding(myObjectClassMethod); + IMP myObjectClassImp = method_getImplementation(myObjectClassMethod); + class_addMethod(subclass, @selector(class), myObjectClassImp, objectClassTypes); + + /* Adding forwarder for most instance methods to allow for verify after run */ + NSArray *methodBlackList = @[@"class", @"forwardingTargetForSelector:", @"methodSignatureForSelector:", @"forwardInvocation:", + @"allowsWeakReference", @"retainWeakReference", @"isBlock", @"retainCount", @"retain", @"release", @"autorelease"]; + [NSObject enumerateMethodsInClass:mockedClass usingBlock:^(Class cls, SEL sel) { + if((cls == [NSObject class]) || (cls == [NSProxy class])) + return; + NSString *className = NSStringFromClass(cls); + NSString *selName = NSStringFromSelector(sel); + if(([className hasPrefix:@"NS"] || [className hasPrefix:@"UI"]) && + ([selName hasPrefix:@"_"] || [selName hasSuffix:@"_"])) + return; + if([methodBlackList containsObject:selName]) + return; + @try + { + [self setupForwarderForSelector:sel]; + } + @catch(NSException *e) + { + // ignore for now + } + }]; +} + +- (void)setupForwarderForSelector:(SEL)sel +{ + SEL aliasSelector = OCMAliasForOriginalSelector(sel); + if(class_getInstanceMethod(object_getClass(realObject), aliasSelector) != NULL) + return; + + Method originalMethod = class_getInstanceMethod(mockedClass, sel); + IMP originalIMP = method_getImplementation(originalMethod); + const char *types = method_getTypeEncoding(originalMethod); + /* Might be NULL if the selector is forwarded to another class */ + // TODO: check the fallback implementation is actually sufficient + if(types == NULL) + types = ([[mockedClass instanceMethodSignatureForSelector:sel] fullObjCTypes]); + + Class subclass = object_getClass([self realObject]); + IMP forwarderIMP = [mockedClass instanceMethodForwarderForSelector:sel]; + class_replaceMethod(subclass, sel, forwarderIMP, types); + class_addMethod(subclass, aliasSelector, originalIMP, types); +} + + +// Implementation of the -class method; return the Class that was reported with [realObject class] prior to mocking +- (Class)classForRealObject +{ + // in here "self" is a reference to the real object, not the mock + OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self); + if(mock == nil) + [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self]; + return [mock mockedClass]; +} + + +- (id)forwardingTargetForSelectorForRealObject:(SEL)sel +{ + // in here "self" is a reference to the real object, not the mock + OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self); + if(mock == nil) + [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self]; + if([mock handleSelector:sel]) + return self; + + return [self ocmock_replaced_forwardingTargetForSelector:sel]; +} + +// Make the compiler happy in -forwardingTargetForSelectorForRealObject: because it can't find the message… +- (id)ocmock_replaced_forwardingTargetForSelector:(SEL)sel +{ + return nil; +} + + +- (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation +{ + // in here "self" is a reference to the real object, not the mock + OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self); + if(mock == nil) + [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self]; + + if([mock handleInvocation:anInvocation] == NO) + { + [anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation selector])]; + [anInvocation invoke]; + } +} + + +@end diff --git a/Pods/OCMock/Source/OCMock/OCProtocolMockObject.h b/Pods/OCMock/Source/OCMock/OCProtocolMockObject.h new file mode 100644 index 000000000..0b3f6b127 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCProtocolMockObject.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2005-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCProtocolMockObject : OCMockObject +{ + Protocol *mockedProtocol; +} + +- (id)initWithProtocol:(Protocol *)aProtocol; + +@end + diff --git a/Pods/OCMock/Source/OCMock/OCProtocolMockObject.m b/Pods/OCMock/Source/OCMock/OCProtocolMockObject.m new file mode 100644 index 000000000..55dc7b528 --- /dev/null +++ b/Pods/OCMock/Source/OCMock/OCProtocolMockObject.m @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2005-2016 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "NSMethodSignature+OCMAdditions.h" +#import "OCProtocolMockObject.h" + +@implementation OCProtocolMockObject + +#pragma mark Initialisers, description, accessors, etc. + +- (id)initWithProtocol:(Protocol *)aProtocol +{ + NSParameterAssert(aProtocol != nil); + [super init]; + mockedProtocol = aProtocol; + return self; +} + +- (NSString *)description +{ + const char* name = protocol_getName(mockedProtocol); + return [NSString stringWithFormat:@"OCMockObject(%s)", name]; +} + +#pragma mark Proxy API + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + struct { BOOL isRequired; BOOL isInstance; } opts[4] = { {YES, YES}, {NO, YES}, {YES, NO}, {NO, NO} }; + for(int i = 0; i < 4; i++) + { + struct objc_method_description methodDescription = protocol_getMethodDescription(mockedProtocol, aSelector, opts[i].isRequired, opts[i].isInstance); + if(methodDescription.name != NULL) + return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; + } + return nil; +} + +- (BOOL)conformsToProtocol:(Protocol *)aProtocol +{ + return protocol_conformsToProtocol(mockedProtocol, aProtocol); +} + +- (BOOL)respondsToSelector:(SEL)selector +{ + return ([self methodSignatureForSelector:selector] != nil); +} + +@end diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 000000000..d5577f892 --- /dev/null +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,799 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 03F5EE857E2BAF72AB6BEBE8C08F477E /* OCMNotificationPoster.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CA6A5763FEA7E7A0A3B69677F1C3F10 /* OCMNotificationPoster.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 0AC63A4E1C49A08DF0038A2CB578AEF9 /* OCMRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = E335CE147F4FCC4E93A585A0688083D2 /* OCMRecorder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 12E68D9F5B6EC5840BD702652CE35197 /* OCMInvocationStub.h in Headers */ = {isa = PBXBuildFile; fileRef = CA20AD763085232EB811ADB451250CFE /* OCMInvocationStub.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1C39AB1D67D218284DC3AE22E6401361 /* OCMExceptionReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = C5D1E6244E73ACDAD3F6B49B7592B7F6 /* OCMExceptionReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1E2A24A52433917446348A64D586073D /* OCMBlockCaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DEFB3CA39480952785E373FFC61A970 /* OCMBlockCaller.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 1F4D5DB6CB3F389BEB36A2835B94C6D5 /* OCMock.h in Headers */ = {isa = PBXBuildFile; fileRef = FFB97AC1C7396228E044FF8431B454C2 /* OCMock.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A8173E720F12D5F9CCD6001D0228D2D /* OCMArgAction.m in Sources */ = {isa = PBXBuildFile; fileRef = AE093FF8DAAFC0AE4129BE706CF3D49D /* OCMArgAction.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 2ADADEC378A839F5AE006E3523EC265F /* OCPartialMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D6BF69075B157BF8BC3287C893599B15 /* OCPartialMockObject.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2D125712F3D241F8D8C1A9B7EE0478C6 /* OCMInvocationExpectation.h in Headers */ = {isa = PBXBuildFile; fileRef = F204A5C827EC85783599AC240E31A949 /* OCMInvocationExpectation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 346B6D672C01EE3DD8EE16B04229F183 /* OCMRealObjectForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 60EA7631C4547D4450BC19ED6FD1D662 /* OCMRealObjectForwarder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 409435B4AEA9DC5CD528509299026001 /* OCMObserverRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = E0D8DE538FDA5D3E664C5ADD23CC24E5 /* OCMObserverRecorder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 416C2DD9E7038656F7A4281A2EFF699B /* OCPartialMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C50BBB18EFAF1190419B602AE585FE81 /* OCPartialMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 42498CF985FE0CE9DD90DD683266CC18 /* OCMInvocationExpectation.m in Sources */ = {isa = PBXBuildFile; fileRef = BF7F882D3201BE8599F20865B78F0FE9 /* OCMInvocationExpectation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 48DDD9BE0005FDDF9C91EBE57B03D1D4 /* OCMIndirectReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = E808E4218926AE48D170B4FBDE4CFC5E /* OCMIndirectReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 49DB462C455568591A759157D2850B86 /* OCObserverMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = EB55F4C5E7223618F9F856AB203E40AA /* OCObserverMockObject.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4E91FC9241DAF2AFAC03983082025C10 /* OCMReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D1FAB30430DED5EA1A357096C2D353B /* OCMReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4ECCDEF1D4BA8D11BCA5E7BBF29DC427 /* NSMethodSignature+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C25193A5E9C61D1794961715EDE9167 /* NSMethodSignature+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 55107C79C22E46BA0E03017D2CD2E18B /* OCMBlockArgCaller.m in Sources */ = {isa = PBXBuildFile; fileRef = B6798923DD4F2989DAA1B7B5B4435EAD /* OCMBlockArgCaller.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 5AD200E0282948E9370C7E762E6E2EED /* NSValue+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B9B9E4866B3FAD54B5BA8978ED84C0FD /* NSValue+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 5BA53A42B4B0FF9FEEAC39DFB8BBFD5D /* OCMock-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CE89C71BAFB5295AC2D415B32ABDCF94 /* OCMock-dummy.m */; }; + 5D7A5ED2A4EC0BE072A11A5DA7B878A5 /* NSMethodSignature+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = A9972E0EB79E284AA82925084077E738 /* NSMethodSignature+OCMAdditions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5F95FFA001F7D021C9D3D0D1262F4BD2 /* NSValue+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 42215B7B41E6269E6C9FBEB695D7686E /* NSValue+OCMAdditions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 61951F55E72A85F89699527A9C69901E /* NSNotificationCenter+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F462FE47AF1DFE8D35CDD66D12A06F0 /* NSNotificationCenter+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 65BD2EAA1888CC88B8CA7E28B4F9EAEB /* OCMLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 78828399BAAE423C4724C9533F5382ED /* OCMLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 686B378129E94577605E62FD1572EFD3 /* OCMExpectationRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 79A1E89D7AD7E000B8104191B7B27BCC /* OCMExpectationRecorder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 69CB28B1446963CE44FD81251FC9B338 /* OCMRealObjectForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 37E416DDC21B2A1738A82E5642E04D23 /* OCMRealObjectForwarder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6CC12BC4E10DAD9B5FF8E2BA41A9C8A0 /* OCMReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C4EADA58A9EB3715F2BDA00E4C36F99 /* OCMReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 6ED60777B8769FB9206AD6DB480C6975 /* OCMExpectationRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 98361CD81CF91315EF11AED46899AF53 /* OCMExpectationRecorder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 71D11C5381BD48EEBE41B761925A7602 /* OCClassMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D9359CB3F2FE8FD51F7987C9C637438B /* OCClassMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 75590CD1E4916B13C22A56FF92B5016F /* NSNotificationCenter+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = FD111DD460FD04F5C37983E3FD5D4EC0 /* NSNotificationCenter+OCMAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 76C11F59C3940979399B3AA9E547E845 /* OCMMacroState.h in Headers */ = {isa = PBXBuildFile; fileRef = 81BE16F4CF360CC1D099CDD695A599E5 /* OCMMacroState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 782A052F46927F486092F43622F6AB2D /* OCMBlockCaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 18F5B34D0FFB5DA6271A15FEA6FE0A62 /* OCMBlockCaller.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 78D00A49953FB8DDC39334CD40B8C713 /* OCMMacroState.m in Sources */ = {isa = PBXBuildFile; fileRef = EC7D9DE3022861A3013E7199979D7C96 /* OCMMacroState.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 797D76D6582EE431937A33BB1E662270 /* OCMInvocationMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 6AD68A9918F423DEDF7BDBC265456306 /* OCMInvocationMatcher.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7AC4A7EABFA9F4EF4EBB76E7545EAB8D /* Pods-IGListKitTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B975B808BE3B597DB8A83E1F66D2F03 /* Pods-IGListKitTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7D7BE573A26FE8D0A5CFA21637505AC1 /* OCMock-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = DE4C7C6F737C71AA76258E5304A4E0A2 /* OCMock-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 828A6CE4F88922FCBAEFD1201C6DE519 /* OCMConstraint.h in Headers */ = {isa = PBXBuildFile; fileRef = E8930E04FAE9BA9BA73980CC786BE8C0 /* OCMConstraint.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84CA30EABB6FFCCC9DCC18991C1192FF /* Pods-IGListKitTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D64AE4B59B897448E443D61866294AB5 /* Pods-IGListKitTests-dummy.m */; }; + 8EF30D6EE3AD90E917AE94C151BE868D /* NSInvocation+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 39EB4FBD5BCC3955049231A027E6C7CD /* NSInvocation+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 9142F67621E2B555A2FEF259E5A5A123 /* OCMInvocationStub.m in Sources */ = {isa = PBXBuildFile; fileRef = B61F3911BD0FB425C80AB982304DE0B2 /* OCMInvocationStub.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 924869A06D77711868AC91B9F896FCDA /* OCMConstraint.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B4B840C9BF509C2FE1EA6170246E5C1 /* OCMConstraint.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 9501D11D86F452C48D5E33525B5C3C35 /* OCMFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = D6DC99DFD40020ABD61383BAD8225562 /* OCMFunctions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C410230DB93AD3BFB98265DF0CD34E2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEC22C73C1608DFA5D5D78BDCB218219 /* Foundation.framework */; }; + A2E4FDFF7E93C1DEC4D6751E4732624C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEC22C73C1608DFA5D5D78BDCB218219 /* Foundation.framework */; }; + A4393F79E3D5628C07E1D9D6F87749F0 /* OCMStubRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = C57DAADD9E64CDE2A65AC521D4854FFE /* OCMStubRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A49832C29637716A807180254EE009F7 /* OCMNotificationPoster.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA65C1F57D866B096BDF75847837CB5 /* OCMNotificationPoster.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A6B7C730B8F621AF9EA9ED8F280C00D0 /* OCMArgAction.h in Headers */ = {isa = PBXBuildFile; fileRef = A9B30FF38C0A6CCDFD2CC460779C887A /* OCMArgAction.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A8C187181C77CEABC83CD9CCFB387089 /* OCClassMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC4BFEE8296742ED75DD86CB40AB71 /* OCClassMockObject.h */; settings = {ATTRIBUTES = (Project, ); }; }; + AA29D6EF9537E1DD8DF16AD0B20BC3D0 /* OCMFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B424290BCF97ED3D0872878957394F0 /* OCMFunctions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + AAAC712F6198BB176F2C3AC396C7AB76 /* NSInvocation+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4380441697183E5A977D7529FA30A1CF /* NSInvocation+OCMAdditions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + ADD2FBEA7C59439D47E6C520109BE1C1 /* OCMPassByRefSetter.h in Headers */ = {isa = PBXBuildFile; fileRef = 11788970FD4F576A2F3F7DB227C42E8A /* OCMPassByRefSetter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B0CE1166586A35238256CDF1A4C588EA /* OCMRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = A82D5159309BE7DE746FF948BBB0386F /* OCMRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B4F869A84D05F56D59840B99292C6FC6 /* OCMIndirectReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = FA9A59FD7C4F619BB5878A38A415A12F /* OCMIndirectReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + BAF23A78B40C32CF7054C7A15C7BF66C /* OCProtocolMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 859324D40F656D9FAC02D350D9E86DCC /* OCProtocolMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + CA486CC72279C489774A7731058948CF /* OCMArg.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B119366CF65475CAADDD43E2402FCDA /* OCMArg.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + CF7C74A41A746CE32F09E3910A0AD1CD /* OCMVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = E0631D3AEC6C764E0FB5D19E9D617C1D /* OCMVerifier.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + D7D6963C385FFEB1612CEFD6DB18F907 /* OCMFunctionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 16CB51379CDB00EB7467E6B4B0C79F2B /* OCMFunctionsPrivate.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D81171BC97B4287B0ED73A967CA8505C /* NSObject+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 88ED5D196ED6695091361EB66B17B516 /* NSObject+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + DBEE68AE9B20851B58D076B6C6D60C3E /* OCProtocolMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 0019314F609F1083CF413C5A482273CD /* OCProtocolMockObject.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DC8ED9E1CC997428EA320567DDE4A1D2 /* OCMExceptionReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = A6A34341D84E14BC5541177000A44363 /* OCMExceptionReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + DCE3332BC15377F7990C2876C3831647 /* OCMInvocationMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BE8A8EF6CACB0B742A6E8F6DB9B5EA7 /* OCMInvocationMatcher.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + DEF2687019D8CF7B8A460439DEDF0B54 /* OCMBoxedReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AF3CC6167DFB2EBB7A8361DD6C6C05F /* OCMBoxedReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E2EC03F8A7E04450C40CA6E47EFE1B7B /* OCMBlockArgCaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 06EA21C3E134F1D54000BCDF7C8664EB /* OCMBlockArgCaller.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E8F4A8B5684E887DC2A52226DA008F00 /* NSObject+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 78E4E4895F865EEF7EA98ED41EA08E5E /* NSObject+OCMAdditions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EA501005DC0DF2F6DBC7B3EBB26A57EE /* OCMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D995B0C49A4DAF4E7F56399094C0EB2 /* OCMockObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EA720C6A097240E4F30D79D675DC01EF /* OCMVerifier.h in Headers */ = {isa = PBXBuildFile; fileRef = C708F9D1AC2A31EC5B88E35AB01D1803 /* OCMVerifier.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EBA47725933A76A885B84EE874FBC421 /* OCObserverMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BD2C8647E50AE8EDA8410F14D68EFA0 /* OCObserverMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + EE4390AA2DA83D5BCC44952F3B004697 /* OCMBoxedReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = F389E14DBE71DB2C71EAF4C876BF62F9 /* OCMBoxedReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + F2E8AFDC237725F30722A5785EE8C3C7 /* OCMLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E391137595D97EE03696803F5E8ED63 /* OCMLocation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + F59647A7B4C72911D933D3C391E473A4 /* OCMObserverRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A9C9C272CA12E76D64E7F458C8D85FF /* OCMObserverRecorder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + F5972D51F6EE335382965B9DDEC675D6 /* OCMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 35DB60D32FDFF43D1FB49565D828CEAA /* OCMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + FB56A2F0614574D57185E69B454F7FF2 /* OCMPassByRefSetter.m in Sources */ = {isa = PBXBuildFile; fileRef = 863114BC8DC62295516F12FCBE22088E /* OCMPassByRefSetter.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + FD1FD979FF45BE2A30A40482565EB768 /* OCMArg.h in Headers */ = {isa = PBXBuildFile; fileRef = E989ED7E432C95C679195590A1557FF0 /* OCMArg.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF82A4FADF612D7C4CCEA900E0840996 /* OCMStubRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = E4AF8AE70C11F95DF78AE7E9ABFB78A1 /* OCMStubRecorder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0732436112059B0819E40CC5700FE35D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0C9D3C758CFA9888936DBEB0F9B3C74D; + remoteInfo = OCMock; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0019314F609F1083CF413C5A482273CD /* OCProtocolMockObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCProtocolMockObject.h; path = Source/OCMock/OCProtocolMockObject.h; sourceTree = ""; }; + 06EA21C3E134F1D54000BCDF7C8664EB /* OCMBlockArgCaller.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMBlockArgCaller.h; path = Source/OCMock/OCMBlockArgCaller.h; sourceTree = ""; }; + 0C4986DFED7588B139B136986CFEE092 /* OCMock.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = OCMock.xcconfig; sourceTree = ""; }; + 0C4EADA58A9EB3715F2BDA00E4C36F99 /* OCMReturnValueProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMReturnValueProvider.m; path = Source/OCMock/OCMReturnValueProvider.m; sourceTree = ""; }; + 0CA6A5763FEA7E7A0A3B69677F1C3F10 /* OCMNotificationPoster.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMNotificationPoster.m; path = Source/OCMock/OCMNotificationPoster.m; sourceTree = ""; }; + 0E391137595D97EE03696803F5E8ED63 /* OCMLocation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMLocation.m; path = Source/OCMock/OCMLocation.m; sourceTree = ""; }; + 0F5E00280A98D635E5FDA97D2A13169B /* Pods-IGListKitTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-IGListKitTests-acknowledgements.markdown"; sourceTree = ""; }; + 11788970FD4F576A2F3F7DB227C42E8A /* OCMPassByRefSetter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMPassByRefSetter.h; path = Source/OCMock/OCMPassByRefSetter.h; sourceTree = ""; }; + 16CB51379CDB00EB7467E6B4B0C79F2B /* OCMFunctionsPrivate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMFunctionsPrivate.h; path = Source/OCMock/OCMFunctionsPrivate.h; sourceTree = ""; }; + 18F5B34D0FFB5DA6271A15FEA6FE0A62 /* OCMBlockCaller.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMBlockCaller.h; path = Source/OCMock/OCMBlockCaller.h; sourceTree = ""; }; + 1B119366CF65475CAADDD43E2402FCDA /* OCMArg.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMArg.m; path = Source/OCMock/OCMArg.m; sourceTree = ""; }; + 1B4B840C9BF509C2FE1EA6170246E5C1 /* OCMConstraint.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMConstraint.m; path = Source/OCMock/OCMConstraint.m; sourceTree = ""; }; + 1BD2C8647E50AE8EDA8410F14D68EFA0 /* OCObserverMockObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCObserverMockObject.m; path = Source/OCMock/OCObserverMockObject.m; sourceTree = ""; }; + 1D995B0C49A4DAF4E7F56399094C0EB2 /* OCMockObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMockObject.h; path = Source/OCMock/OCMockObject.h; sourceTree = ""; }; + 21B7615E23C723CA8BA1A02A36F0259A /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2B424290BCF97ED3D0872878957394F0 /* OCMFunctions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMFunctions.m; path = Source/OCMock/OCMFunctions.m; sourceTree = ""; }; + 2DA65C1F57D866B096BDF75847837CB5 /* OCMNotificationPoster.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMNotificationPoster.h; path = Source/OCMock/OCMNotificationPoster.h; sourceTree = ""; }; + 2EDB77F77C9DB890CA518BE896411443 /* Pods_IGListKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IGListKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 35DB60D32FDFF43D1FB49565D828CEAA /* OCMockObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMockObject.m; path = Source/OCMock/OCMockObject.m; sourceTree = ""; }; + 37E416DDC21B2A1738A82E5642E04D23 /* OCMRealObjectForwarder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMRealObjectForwarder.h; path = Source/OCMock/OCMRealObjectForwarder.h; sourceTree = ""; }; + 39EB4FBD5BCC3955049231A027E6C7CD /* NSInvocation+OCMAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSInvocation+OCMAdditions.m"; path = "Source/OCMock/NSInvocation+OCMAdditions.m"; sourceTree = ""; }; + 3DDC4BFEE8296742ED75DD86CB40AB71 /* OCClassMockObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCClassMockObject.h; path = Source/OCMock/OCClassMockObject.h; sourceTree = ""; }; + 42215B7B41E6269E6C9FBEB695D7686E /* NSValue+OCMAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSValue+OCMAdditions.h"; path = "Source/OCMock/NSValue+OCMAdditions.h"; sourceTree = ""; }; + 4376A8B032775766102EF9FFC2F0C5C8 /* Pods-IGListKitTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-IGListKitTests-acknowledgements.plist"; sourceTree = ""; }; + 4380441697183E5A977D7529FA30A1CF /* NSInvocation+OCMAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSInvocation+OCMAdditions.h"; path = "Source/OCMock/NSInvocation+OCMAdditions.h"; sourceTree = ""; }; + 4C25193A5E9C61D1794961715EDE9167 /* NSMethodSignature+OCMAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSMethodSignature+OCMAdditions.m"; path = "Source/OCMock/NSMethodSignature+OCMAdditions.m"; sourceTree = ""; }; + 5E7BECB422D10AE40510522E186389FD /* OCMock.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = OCMock.modulemap; sourceTree = ""; }; + 60EA7631C4547D4450BC19ED6FD1D662 /* OCMRealObjectForwarder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMRealObjectForwarder.m; path = Source/OCMock/OCMRealObjectForwarder.m; sourceTree = ""; }; + 6AD68A9918F423DEDF7BDBC265456306 /* OCMInvocationMatcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMInvocationMatcher.h; path = Source/OCMock/OCMInvocationMatcher.h; sourceTree = ""; }; + 6D1FAB30430DED5EA1A357096C2D353B /* OCMReturnValueProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMReturnValueProvider.h; path = Source/OCMock/OCMReturnValueProvider.h; sourceTree = ""; }; + 6DEFB3CA39480952785E373FFC61A970 /* OCMBlockCaller.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMBlockCaller.m; path = Source/OCMock/OCMBlockCaller.m; sourceTree = ""; }; + 714E8F53BA7277A3EB4FEECA54249CB2 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 74506B8BA0C4A232263F118B529EA7D8 /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 78828399BAAE423C4724C9533F5382ED /* OCMLocation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMLocation.h; path = Source/OCMock/OCMLocation.h; sourceTree = ""; }; + 78E4E4895F865EEF7EA98ED41EA08E5E /* NSObject+OCMAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSObject+OCMAdditions.h"; path = "Source/OCMock/NSObject+OCMAdditions.h"; sourceTree = ""; }; + 79A1E89D7AD7E000B8104191B7B27BCC /* OCMExpectationRecorder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMExpectationRecorder.h; path = Source/OCMock/OCMExpectationRecorder.h; sourceTree = ""; }; + 7A9C9C272CA12E76D64E7F458C8D85FF /* OCMObserverRecorder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMObserverRecorder.m; path = Source/OCMock/OCMObserverRecorder.m; sourceTree = ""; }; + 7B975B808BE3B597DB8A83E1F66D2F03 /* Pods-IGListKitTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-IGListKitTests-umbrella.h"; sourceTree = ""; }; + 7BE8A8EF6CACB0B742A6E8F6DB9B5EA7 /* OCMInvocationMatcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMInvocationMatcher.m; path = Source/OCMock/OCMInvocationMatcher.m; sourceTree = ""; }; + 81BE16F4CF360CC1D099CDD695A599E5 /* OCMMacroState.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMMacroState.h; path = Source/OCMock/OCMMacroState.h; sourceTree = ""; }; + 859324D40F656D9FAC02D350D9E86DCC /* OCProtocolMockObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCProtocolMockObject.m; path = Source/OCMock/OCProtocolMockObject.m; sourceTree = ""; }; + 863114BC8DC62295516F12FCBE22088E /* OCMPassByRefSetter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMPassByRefSetter.m; path = Source/OCMock/OCMPassByRefSetter.m; sourceTree = ""; }; + 88ED5D196ED6695091361EB66B17B516 /* NSObject+OCMAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSObject+OCMAdditions.m"; path = "Source/OCMock/NSObject+OCMAdditions.m"; sourceTree = ""; }; + 8EF01877B25D845B836ADD334BAF6594 /* Pods-IGListKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-IGListKitTests.release.xcconfig"; sourceTree = ""; }; + 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 982A63F5BE4D65D758ABD5E4A3C9067F /* OCMock-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "OCMock-prefix.pch"; sourceTree = ""; }; + 98361CD81CF91315EF11AED46899AF53 /* OCMExpectationRecorder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMExpectationRecorder.m; path = Source/OCMock/OCMExpectationRecorder.m; sourceTree = ""; }; + 99EA226693486AB46E5E82E6F5446AC4 /* Pods-IGListKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-IGListKitTests.debug.xcconfig"; sourceTree = ""; }; + 9AF3CC6167DFB2EBB7A8361DD6C6C05F /* OCMBoxedReturnValueProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMBoxedReturnValueProvider.h; path = Source/OCMock/OCMBoxedReturnValueProvider.h; sourceTree = ""; }; + 9F462FE47AF1DFE8D35CDD66D12A06F0 /* NSNotificationCenter+OCMAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSNotificationCenter+OCMAdditions.m"; path = "Source/OCMock/NSNotificationCenter+OCMAdditions.m"; sourceTree = ""; }; + A6A34341D84E14BC5541177000A44363 /* OCMExceptionReturnValueProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMExceptionReturnValueProvider.m; path = Source/OCMock/OCMExceptionReturnValueProvider.m; sourceTree = ""; }; + A81D99649336CCBF031A5633E24D4D41 /* Pods-IGListKitTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-IGListKitTests.modulemap"; sourceTree = ""; }; + A82D5159309BE7DE746FF948BBB0386F /* OCMRecorder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMRecorder.h; path = Source/OCMock/OCMRecorder.h; sourceTree = ""; }; + A9972E0EB79E284AA82925084077E738 /* NSMethodSignature+OCMAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSMethodSignature+OCMAdditions.h"; path = "Source/OCMock/NSMethodSignature+OCMAdditions.h"; sourceTree = ""; }; + A9B30FF38C0A6CCDFD2CC460779C887A /* OCMArgAction.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMArgAction.h; path = Source/OCMock/OCMArgAction.h; sourceTree = ""; }; + AE093FF8DAAFC0AE4129BE706CF3D49D /* OCMArgAction.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMArgAction.m; path = Source/OCMock/OCMArgAction.m; sourceTree = ""; }; + B61F3911BD0FB425C80AB982304DE0B2 /* OCMInvocationStub.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMInvocationStub.m; path = Source/OCMock/OCMInvocationStub.m; sourceTree = ""; }; + B6798923DD4F2989DAA1B7B5B4435EAD /* OCMBlockArgCaller.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMBlockArgCaller.m; path = Source/OCMock/OCMBlockArgCaller.m; sourceTree = ""; }; + B9B9E4866B3FAD54B5BA8978ED84C0FD /* NSValue+OCMAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSValue+OCMAdditions.m"; path = "Source/OCMock/NSValue+OCMAdditions.m"; sourceTree = ""; }; + BF7F882D3201BE8599F20865B78F0FE9 /* OCMInvocationExpectation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMInvocationExpectation.m; path = Source/OCMock/OCMInvocationExpectation.m; sourceTree = ""; }; + C50BBB18EFAF1190419B602AE585FE81 /* OCPartialMockObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCPartialMockObject.m; path = Source/OCMock/OCPartialMockObject.m; sourceTree = ""; }; + C57DAADD9E64CDE2A65AC521D4854FFE /* OCMStubRecorder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMStubRecorder.h; path = Source/OCMock/OCMStubRecorder.h; sourceTree = ""; }; + C5D1E6244E73ACDAD3F6B49B7592B7F6 /* OCMExceptionReturnValueProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMExceptionReturnValueProvider.h; path = Source/OCMock/OCMExceptionReturnValueProvider.h; sourceTree = ""; }; + C708F9D1AC2A31EC5B88E35AB01D1803 /* OCMVerifier.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMVerifier.h; path = Source/OCMock/OCMVerifier.h; sourceTree = ""; }; + CA20AD763085232EB811ADB451250CFE /* OCMInvocationStub.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMInvocationStub.h; path = Source/OCMock/OCMInvocationStub.h; sourceTree = ""; }; + CE89C71BAFB5295AC2D415B32ABDCF94 /* OCMock-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "OCMock-dummy.m"; sourceTree = ""; }; + CEC22C73C1608DFA5D5D78BDCB218219 /* 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; }; + D64AE4B59B897448E443D61866294AB5 /* Pods-IGListKitTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-IGListKitTests-dummy.m"; sourceTree = ""; }; + D6BF69075B157BF8BC3287C893599B15 /* OCPartialMockObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCPartialMockObject.h; path = Source/OCMock/OCPartialMockObject.h; sourceTree = ""; }; + D6DC99DFD40020ABD61383BAD8225562 /* OCMFunctions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMFunctions.h; path = Source/OCMock/OCMFunctions.h; sourceTree = ""; }; + D9359CB3F2FE8FD51F7987C9C637438B /* OCClassMockObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCClassMockObject.m; path = Source/OCMock/OCClassMockObject.m; sourceTree = ""; }; + DE4C7C6F737C71AA76258E5304A4E0A2 /* OCMock-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "OCMock-umbrella.h"; sourceTree = ""; }; + DEE9D00A89865067AC36B2779FF18902 /* Pods-IGListKitTests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-IGListKitTests-frameworks.sh"; sourceTree = ""; }; + E0631D3AEC6C764E0FB5D19E9D617C1D /* OCMVerifier.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMVerifier.m; path = Source/OCMock/OCMVerifier.m; sourceTree = ""; }; + E0D8DE538FDA5D3E664C5ADD23CC24E5 /* OCMObserverRecorder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMObserverRecorder.h; path = Source/OCMock/OCMObserverRecorder.h; sourceTree = ""; }; + E335CE147F4FCC4E93A585A0688083D2 /* OCMRecorder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMRecorder.m; path = Source/OCMock/OCMRecorder.m; sourceTree = ""; }; + E4AF8AE70C11F95DF78AE7E9ABFB78A1 /* OCMStubRecorder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMStubRecorder.m; path = Source/OCMock/OCMStubRecorder.m; sourceTree = ""; }; + E808E4218926AE48D170B4FBDE4CFC5E /* OCMIndirectReturnValueProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMIndirectReturnValueProvider.h; path = Source/OCMock/OCMIndirectReturnValueProvider.h; sourceTree = ""; }; + E8930E04FAE9BA9BA73980CC786BE8C0 /* OCMConstraint.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMConstraint.h; path = Source/OCMock/OCMConstraint.h; sourceTree = ""; }; + E989ED7E432C95C679195590A1557FF0 /* OCMArg.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMArg.h; path = Source/OCMock/OCMArg.h; sourceTree = ""; }; + EB55F4C5E7223618F9F856AB203E40AA /* OCObserverMockObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCObserverMockObject.h; path = Source/OCMock/OCObserverMockObject.h; sourceTree = ""; }; + EC7D9DE3022861A3013E7199979D7C96 /* OCMMacroState.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMMacroState.m; path = Source/OCMock/OCMMacroState.m; sourceTree = ""; }; + F204A5C827EC85783599AC240E31A949 /* OCMInvocationExpectation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMInvocationExpectation.h; path = Source/OCMock/OCMInvocationExpectation.h; sourceTree = ""; }; + F389E14DBE71DB2C71EAF4C876BF62F9 /* OCMBoxedReturnValueProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMBoxedReturnValueProvider.m; path = Source/OCMock/OCMBoxedReturnValueProvider.m; sourceTree = ""; }; + FA9A59FD7C4F619BB5878A38A415A12F /* OCMIndirectReturnValueProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMIndirectReturnValueProvider.m; path = Source/OCMock/OCMIndirectReturnValueProvider.m; sourceTree = ""; }; + FD111DD460FD04F5C37983E3FD5D4EC0 /* NSNotificationCenter+OCMAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSNotificationCenter+OCMAdditions.h"; path = "Source/OCMock/NSNotificationCenter+OCMAdditions.h"; sourceTree = ""; }; + FDA15F7FA6BFBBE1C5085D5AC1D028C1 /* Pods-IGListKitTests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-IGListKitTests-resources.sh"; sourceTree = ""; }; + FFB97AC1C7396228E044FF8431B454C2 /* OCMock.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMock.h; path = Source/OCMock/OCMock.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + ADC08300FC9577494B7689A71CA35AF6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A2E4FDFF7E93C1DEC4D6751E4732624C /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E2AEAD73E1A3320AFE1A3D4679BDB94F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9C410230DB93AD3BFB98265DF0CD34E2 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3DCAB2B7CDE207B3958B6CB957FCC758 /* iOS */ = { + isa = PBXGroup; + children = ( + CEC22C73C1608DFA5D5D78BDCB218219 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 635C585E46EEBA10F04C631B779EBEE6 /* Pods */ = { + isa = PBXGroup; + children = ( + A59CBDDD942954C97809A7A6E34474F8 /* OCMock */, + ); + name = Pods; + sourceTree = ""; + }; + 7552A9B19C130C69294D11B33940BC3E /* Support Files */ = { + isa = PBXGroup; + children = ( + 714E8F53BA7277A3EB4FEECA54249CB2 /* Info.plist */, + 5E7BECB422D10AE40510522E186389FD /* OCMock.modulemap */, + 0C4986DFED7588B139B136986CFEE092 /* OCMock.xcconfig */, + CE89C71BAFB5295AC2D415B32ABDCF94 /* OCMock-dummy.m */, + 982A63F5BE4D65D758ABD5E4A3C9067F /* OCMock-prefix.pch */, + DE4C7C6F737C71AA76258E5304A4E0A2 /* OCMock-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/OCMock"; + sourceTree = ""; + }; + 7DB346D0F39D3F0E887471402A8071AB = { + isa = PBXGroup; + children = ( + 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */, + BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */, + 635C585E46EEBA10F04C631B779EBEE6 /* Pods */, + CF9166618D94C8E42C03D834E99BEF56 /* Products */, + C0B5258A6302277346BCB2F4133E2B86 /* Targets Support Files */, + ); + sourceTree = ""; + }; + 8B96BB1FB68144812A11A847D93CA4B0 /* Pods-IGListKitTests */ = { + isa = PBXGroup; + children = ( + 21B7615E23C723CA8BA1A02A36F0259A /* Info.plist */, + A81D99649336CCBF031A5633E24D4D41 /* Pods-IGListKitTests.modulemap */, + 0F5E00280A98D635E5FDA97D2A13169B /* Pods-IGListKitTests-acknowledgements.markdown */, + 4376A8B032775766102EF9FFC2F0C5C8 /* Pods-IGListKitTests-acknowledgements.plist */, + D64AE4B59B897448E443D61866294AB5 /* Pods-IGListKitTests-dummy.m */, + DEE9D00A89865067AC36B2779FF18902 /* Pods-IGListKitTests-frameworks.sh */, + FDA15F7FA6BFBBE1C5085D5AC1D028C1 /* Pods-IGListKitTests-resources.sh */, + 7B975B808BE3B597DB8A83E1F66D2F03 /* Pods-IGListKitTests-umbrella.h */, + 99EA226693486AB46E5E82E6F5446AC4 /* Pods-IGListKitTests.debug.xcconfig */, + 8EF01877B25D845B836ADD334BAF6594 /* Pods-IGListKitTests.release.xcconfig */, + ); + name = "Pods-IGListKitTests"; + path = "Target Support Files/Pods-IGListKitTests"; + sourceTree = ""; + }; + A59CBDDD942954C97809A7A6E34474F8 /* OCMock */ = { + isa = PBXGroup; + children = ( + 4380441697183E5A977D7529FA30A1CF /* NSInvocation+OCMAdditions.h */, + 39EB4FBD5BCC3955049231A027E6C7CD /* NSInvocation+OCMAdditions.m */, + A9972E0EB79E284AA82925084077E738 /* NSMethodSignature+OCMAdditions.h */, + 4C25193A5E9C61D1794961715EDE9167 /* NSMethodSignature+OCMAdditions.m */, + FD111DD460FD04F5C37983E3FD5D4EC0 /* NSNotificationCenter+OCMAdditions.h */, + 9F462FE47AF1DFE8D35CDD66D12A06F0 /* NSNotificationCenter+OCMAdditions.m */, + 78E4E4895F865EEF7EA98ED41EA08E5E /* NSObject+OCMAdditions.h */, + 88ED5D196ED6695091361EB66B17B516 /* NSObject+OCMAdditions.m */, + 42215B7B41E6269E6C9FBEB695D7686E /* NSValue+OCMAdditions.h */, + B9B9E4866B3FAD54B5BA8978ED84C0FD /* NSValue+OCMAdditions.m */, + 3DDC4BFEE8296742ED75DD86CB40AB71 /* OCClassMockObject.h */, + D9359CB3F2FE8FD51F7987C9C637438B /* OCClassMockObject.m */, + E989ED7E432C95C679195590A1557FF0 /* OCMArg.h */, + 1B119366CF65475CAADDD43E2402FCDA /* OCMArg.m */, + A9B30FF38C0A6CCDFD2CC460779C887A /* OCMArgAction.h */, + AE093FF8DAAFC0AE4129BE706CF3D49D /* OCMArgAction.m */, + 06EA21C3E134F1D54000BCDF7C8664EB /* OCMBlockArgCaller.h */, + B6798923DD4F2989DAA1B7B5B4435EAD /* OCMBlockArgCaller.m */, + 18F5B34D0FFB5DA6271A15FEA6FE0A62 /* OCMBlockCaller.h */, + 6DEFB3CA39480952785E373FFC61A970 /* OCMBlockCaller.m */, + 9AF3CC6167DFB2EBB7A8361DD6C6C05F /* OCMBoxedReturnValueProvider.h */, + F389E14DBE71DB2C71EAF4C876BF62F9 /* OCMBoxedReturnValueProvider.m */, + E8930E04FAE9BA9BA73980CC786BE8C0 /* OCMConstraint.h */, + 1B4B840C9BF509C2FE1EA6170246E5C1 /* OCMConstraint.m */, + C5D1E6244E73ACDAD3F6B49B7592B7F6 /* OCMExceptionReturnValueProvider.h */, + A6A34341D84E14BC5541177000A44363 /* OCMExceptionReturnValueProvider.m */, + 79A1E89D7AD7E000B8104191B7B27BCC /* OCMExpectationRecorder.h */, + 98361CD81CF91315EF11AED46899AF53 /* OCMExpectationRecorder.m */, + D6DC99DFD40020ABD61383BAD8225562 /* OCMFunctions.h */, + 2B424290BCF97ED3D0872878957394F0 /* OCMFunctions.m */, + 16CB51379CDB00EB7467E6B4B0C79F2B /* OCMFunctionsPrivate.h */, + E808E4218926AE48D170B4FBDE4CFC5E /* OCMIndirectReturnValueProvider.h */, + FA9A59FD7C4F619BB5878A38A415A12F /* OCMIndirectReturnValueProvider.m */, + F204A5C827EC85783599AC240E31A949 /* OCMInvocationExpectation.h */, + BF7F882D3201BE8599F20865B78F0FE9 /* OCMInvocationExpectation.m */, + 6AD68A9918F423DEDF7BDBC265456306 /* OCMInvocationMatcher.h */, + 7BE8A8EF6CACB0B742A6E8F6DB9B5EA7 /* OCMInvocationMatcher.m */, + CA20AD763085232EB811ADB451250CFE /* OCMInvocationStub.h */, + B61F3911BD0FB425C80AB982304DE0B2 /* OCMInvocationStub.m */, + 78828399BAAE423C4724C9533F5382ED /* OCMLocation.h */, + 0E391137595D97EE03696803F5E8ED63 /* OCMLocation.m */, + 81BE16F4CF360CC1D099CDD695A599E5 /* OCMMacroState.h */, + EC7D9DE3022861A3013E7199979D7C96 /* OCMMacroState.m */, + 2DA65C1F57D866B096BDF75847837CB5 /* OCMNotificationPoster.h */, + 0CA6A5763FEA7E7A0A3B69677F1C3F10 /* OCMNotificationPoster.m */, + E0D8DE538FDA5D3E664C5ADD23CC24E5 /* OCMObserverRecorder.h */, + 7A9C9C272CA12E76D64E7F458C8D85FF /* OCMObserverRecorder.m */, + FFB97AC1C7396228E044FF8431B454C2 /* OCMock.h */, + 1D995B0C49A4DAF4E7F56399094C0EB2 /* OCMockObject.h */, + 35DB60D32FDFF43D1FB49565D828CEAA /* OCMockObject.m */, + 11788970FD4F576A2F3F7DB227C42E8A /* OCMPassByRefSetter.h */, + 863114BC8DC62295516F12FCBE22088E /* OCMPassByRefSetter.m */, + 37E416DDC21B2A1738A82E5642E04D23 /* OCMRealObjectForwarder.h */, + 60EA7631C4547D4450BC19ED6FD1D662 /* OCMRealObjectForwarder.m */, + A82D5159309BE7DE746FF948BBB0386F /* OCMRecorder.h */, + E335CE147F4FCC4E93A585A0688083D2 /* OCMRecorder.m */, + 6D1FAB30430DED5EA1A357096C2D353B /* OCMReturnValueProvider.h */, + 0C4EADA58A9EB3715F2BDA00E4C36F99 /* OCMReturnValueProvider.m */, + C57DAADD9E64CDE2A65AC521D4854FFE /* OCMStubRecorder.h */, + E4AF8AE70C11F95DF78AE7E9ABFB78A1 /* OCMStubRecorder.m */, + C708F9D1AC2A31EC5B88E35AB01D1803 /* OCMVerifier.h */, + E0631D3AEC6C764E0FB5D19E9D617C1D /* OCMVerifier.m */, + EB55F4C5E7223618F9F856AB203E40AA /* OCObserverMockObject.h */, + 1BD2C8647E50AE8EDA8410F14D68EFA0 /* OCObserverMockObject.m */, + D6BF69075B157BF8BC3287C893599B15 /* OCPartialMockObject.h */, + C50BBB18EFAF1190419B602AE585FE81 /* OCPartialMockObject.m */, + 0019314F609F1083CF413C5A482273CD /* OCProtocolMockObject.h */, + 859324D40F656D9FAC02D350D9E86DCC /* OCProtocolMockObject.m */, + 7552A9B19C130C69294D11B33940BC3E /* Support Files */, + ); + path = OCMock; + sourceTree = ""; + }; + BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3DCAB2B7CDE207B3958B6CB957FCC758 /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + C0B5258A6302277346BCB2F4133E2B86 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 8B96BB1FB68144812A11A847D93CA4B0 /* Pods-IGListKitTests */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + CF9166618D94C8E42C03D834E99BEF56 /* Products */ = { + isa = PBXGroup; + children = ( + 74506B8BA0C4A232263F118B529EA7D8 /* OCMock.framework */, + 2EDB77F77C9DB890CA518BE896411443 /* Pods_IGListKitTests.framework */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 3531B570F1B0E1BB9B70915307F104AF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 7AC4A7EABFA9F4EF4EBB76E7545EAB8D /* Pods-IGListKitTests-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 73FF773DE9F8A9655C921C5CC24E1D65 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + AAAC712F6198BB176F2C3AC396C7AB76 /* NSInvocation+OCMAdditions.h in Headers */, + 5D7A5ED2A4EC0BE072A11A5DA7B878A5 /* NSMethodSignature+OCMAdditions.h in Headers */, + 75590CD1E4916B13C22A56FF92B5016F /* NSNotificationCenter+OCMAdditions.h in Headers */, + E8F4A8B5684E887DC2A52226DA008F00 /* NSObject+OCMAdditions.h in Headers */, + 5F95FFA001F7D021C9D3D0D1262F4BD2 /* NSValue+OCMAdditions.h in Headers */, + A8C187181C77CEABC83CD9CCFB387089 /* OCClassMockObject.h in Headers */, + FD1FD979FF45BE2A30A40482565EB768 /* OCMArg.h in Headers */, + A6B7C730B8F621AF9EA9ED8F280C00D0 /* OCMArgAction.h in Headers */, + E2EC03F8A7E04450C40CA6E47EFE1B7B /* OCMBlockArgCaller.h in Headers */, + 782A052F46927F486092F43622F6AB2D /* OCMBlockCaller.h in Headers */, + DEF2687019D8CF7B8A460439DEDF0B54 /* OCMBoxedReturnValueProvider.h in Headers */, + 828A6CE4F88922FCBAEFD1201C6DE519 /* OCMConstraint.h in Headers */, + 1C39AB1D67D218284DC3AE22E6401361 /* OCMExceptionReturnValueProvider.h in Headers */, + 686B378129E94577605E62FD1572EFD3 /* OCMExpectationRecorder.h in Headers */, + 9501D11D86F452C48D5E33525B5C3C35 /* OCMFunctions.h in Headers */, + D7D6963C385FFEB1612CEFD6DB18F907 /* OCMFunctionsPrivate.h in Headers */, + 48DDD9BE0005FDDF9C91EBE57B03D1D4 /* OCMIndirectReturnValueProvider.h in Headers */, + 2D125712F3D241F8D8C1A9B7EE0478C6 /* OCMInvocationExpectation.h in Headers */, + 797D76D6582EE431937A33BB1E662270 /* OCMInvocationMatcher.h in Headers */, + 12E68D9F5B6EC5840BD702652CE35197 /* OCMInvocationStub.h in Headers */, + 65BD2EAA1888CC88B8CA7E28B4F9EAEB /* OCMLocation.h in Headers */, + 76C11F59C3940979399B3AA9E547E845 /* OCMMacroState.h in Headers */, + A49832C29637716A807180254EE009F7 /* OCMNotificationPoster.h in Headers */, + 409435B4AEA9DC5CD528509299026001 /* OCMObserverRecorder.h in Headers */, + 7D7BE573A26FE8D0A5CFA21637505AC1 /* OCMock-umbrella.h in Headers */, + 1F4D5DB6CB3F389BEB36A2835B94C6D5 /* OCMock.h in Headers */, + EA501005DC0DF2F6DBC7B3EBB26A57EE /* OCMockObject.h in Headers */, + ADD2FBEA7C59439D47E6C520109BE1C1 /* OCMPassByRefSetter.h in Headers */, + 69CB28B1446963CE44FD81251FC9B338 /* OCMRealObjectForwarder.h in Headers */, + B0CE1166586A35238256CDF1A4C588EA /* OCMRecorder.h in Headers */, + 4E91FC9241DAF2AFAC03983082025C10 /* OCMReturnValueProvider.h in Headers */, + A4393F79E3D5628C07E1D9D6F87749F0 /* OCMStubRecorder.h in Headers */, + EA720C6A097240E4F30D79D675DC01EF /* OCMVerifier.h in Headers */, + 49DB462C455568591A759157D2850B86 /* OCObserverMockObject.h in Headers */, + 2ADADEC378A839F5AE006E3523EC265F /* OCPartialMockObject.h in Headers */, + DBEE68AE9B20851B58D076B6C6D60C3E /* OCProtocolMockObject.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 0C9D3C758CFA9888936DBEB0F9B3C74D /* OCMock */ = { + isa = PBXNativeTarget; + buildConfigurationList = 74A9F74278E1CDC3C61B386FC80DA38B /* Build configuration list for PBXNativeTarget "OCMock" */; + buildPhases = ( + ABF2778C64D22F5A51675036ECE221F3 /* Sources */, + E2AEAD73E1A3320AFE1A3D4679BDB94F /* Frameworks */, + 73FF773DE9F8A9655C921C5CC24E1D65 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OCMock; + productName = OCMock; + productReference = 74506B8BA0C4A232263F118B529EA7D8 /* OCMock.framework */; + productType = "com.apple.product-type.framework"; + }; + 2BCC12417C3B33059F4751067051DE91 /* Pods-IGListKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E6C382B812BE7E838EED8B32720F5831 /* Build configuration list for PBXNativeTarget "Pods-IGListKitTests" */; + buildPhases = ( + 7423D915C08F211F58A4AD60A362EF93 /* Sources */, + ADC08300FC9577494B7689A71CA35AF6 /* Frameworks */, + 3531B570F1B0E1BB9B70915307F104AF /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + A027B779D5068651F2CD3D5696E51D4E /* PBXTargetDependency */, + ); + name = "Pods-IGListKitTests"; + productName = "Pods-IGListKitTests"; + productReference = 2EDB77F77C9DB890CA518BE896411443 /* Pods_IGListKitTests.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 = CF9166618D94C8E42C03D834E99BEF56 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0C9D3C758CFA9888936DBEB0F9B3C74D /* OCMock */, + 2BCC12417C3B33059F4751067051DE91 /* Pods-IGListKitTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 7423D915C08F211F58A4AD60A362EF93 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 84CA30EABB6FFCCC9DCC18991C1192FF /* Pods-IGListKitTests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ABF2778C64D22F5A51675036ECE221F3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8EF30D6EE3AD90E917AE94C151BE868D /* NSInvocation+OCMAdditions.m in Sources */, + 4ECCDEF1D4BA8D11BCA5E7BBF29DC427 /* NSMethodSignature+OCMAdditions.m in Sources */, + 61951F55E72A85F89699527A9C69901E /* NSNotificationCenter+OCMAdditions.m in Sources */, + D81171BC97B4287B0ED73A967CA8505C /* NSObject+OCMAdditions.m in Sources */, + 5AD200E0282948E9370C7E762E6E2EED /* NSValue+OCMAdditions.m in Sources */, + 71D11C5381BD48EEBE41B761925A7602 /* OCClassMockObject.m in Sources */, + CA486CC72279C489774A7731058948CF /* OCMArg.m in Sources */, + 2A8173E720F12D5F9CCD6001D0228D2D /* OCMArgAction.m in Sources */, + 55107C79C22E46BA0E03017D2CD2E18B /* OCMBlockArgCaller.m in Sources */, + 1E2A24A52433917446348A64D586073D /* OCMBlockCaller.m in Sources */, + EE4390AA2DA83D5BCC44952F3B004697 /* OCMBoxedReturnValueProvider.m in Sources */, + 924869A06D77711868AC91B9F896FCDA /* OCMConstraint.m in Sources */, + DC8ED9E1CC997428EA320567DDE4A1D2 /* OCMExceptionReturnValueProvider.m in Sources */, + 6ED60777B8769FB9206AD6DB480C6975 /* OCMExpectationRecorder.m in Sources */, + AA29D6EF9537E1DD8DF16AD0B20BC3D0 /* OCMFunctions.m in Sources */, + B4F869A84D05F56D59840B99292C6FC6 /* OCMIndirectReturnValueProvider.m in Sources */, + 42498CF985FE0CE9DD90DD683266CC18 /* OCMInvocationExpectation.m in Sources */, + DCE3332BC15377F7990C2876C3831647 /* OCMInvocationMatcher.m in Sources */, + 9142F67621E2B555A2FEF259E5A5A123 /* OCMInvocationStub.m in Sources */, + F2E8AFDC237725F30722A5785EE8C3C7 /* OCMLocation.m in Sources */, + 78D00A49953FB8DDC39334CD40B8C713 /* OCMMacroState.m in Sources */, + 03F5EE857E2BAF72AB6BEBE8C08F477E /* OCMNotificationPoster.m in Sources */, + F59647A7B4C72911D933D3C391E473A4 /* OCMObserverRecorder.m in Sources */, + 5BA53A42B4B0FF9FEEAC39DFB8BBFD5D /* OCMock-dummy.m in Sources */, + F5972D51F6EE335382965B9DDEC675D6 /* OCMockObject.m in Sources */, + FB56A2F0614574D57185E69B454F7FF2 /* OCMPassByRefSetter.m in Sources */, + 346B6D672C01EE3DD8EE16B04229F183 /* OCMRealObjectForwarder.m in Sources */, + 0AC63A4E1C49A08DF0038A2CB578AEF9 /* OCMRecorder.m in Sources */, + 6CC12BC4E10DAD9B5FF8E2BA41A9C8A0 /* OCMReturnValueProvider.m in Sources */, + FF82A4FADF612D7C4CCEA900E0840996 /* OCMStubRecorder.m in Sources */, + CF7C74A41A746CE32F09E3910A0AD1CD /* OCMVerifier.m in Sources */, + EBA47725933A76A885B84EE874FBC421 /* OCObserverMockObject.m in Sources */, + 416C2DD9E7038656F7A4281A2EFF699B /* OCPartialMockObject.m in Sources */, + BAF23A78B40C32CF7054C7A15C7BF66C /* OCProtocolMockObject.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + A027B779D5068651F2CD3D5696E51D4E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = OCMock; + target = 0C9D3C758CFA9888936DBEB0F9B3C74D /* OCMock */; + targetProxy = 0732436112059B0819E40CC5700FE35D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 015A368F878AC3E2CEAE21DDE8026304 /* 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; + CODE_SIGNING_REQUIRED = NO; + 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 = 8.0; + ONLY_ACTIVE_ARCH = YES; + PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + 44CDBB6D11DE06DB64D6268622BDC47E /* 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; + CODE_SIGNING_REQUIRED = NO; + 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 = 8.0; + PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6157C8692FDCB756E8E8BB0CEDE6FCEE /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8EF01877B25D845B836ADD334BAF6594 /* Pods-IGListKitTests.release.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + 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-IGListKitTests/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.modulemap"; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = Pods_IGListKitTests; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + A05B55754F89C011BC85D9AC4922C2EE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 99EA226693486AB46E5E82E6F5446AC4 /* Pods-IGListKitTests.debug.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + 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-IGListKitTests/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.modulemap"; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = Pods_IGListKitTests; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + DB9F4B3D5480E3666D2025C2E1F1D87B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0C4986DFED7588B139B136986CFEE092 /* OCMock.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + 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/OCMock/OCMock-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/OCMock/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/OCMock/OCMock.modulemap"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = OCMock; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + FD4D35FF21E6D89F36CD774E0943990F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0C4986DFED7588B139B136986CFEE092 /* OCMock.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + 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/OCMock/OCMock-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/OCMock/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/OCMock/OCMock.modulemap"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = OCMock; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 015A368F878AC3E2CEAE21DDE8026304 /* Debug */, + 44CDBB6D11DE06DB64D6268622BDC47E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 74A9F74278E1CDC3C61B386FC80DA38B /* Build configuration list for PBXNativeTarget "OCMock" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FD4D35FF21E6D89F36CD774E0943990F /* Debug */, + DB9F4B3D5480E3666D2025C2E1F1D87B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E6C382B812BE7E838EED8B32720F5831 /* Build configuration list for PBXNativeTarget "Pods-IGListKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A05B55754F89C011BC85D9AC4922C2EE /* Debug */, + 6157C8692FDCB756E8E8BB0CEDE6FCEE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; +} diff --git a/Pods/Target Support Files/OCMock/Info.plist b/Pods/Target Support Files/OCMock/Info.plist new file mode 100644 index 000000000..d4b3c2942 --- /dev/null +++ b/Pods/Target Support Files/OCMock/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.3.1 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/OCMock/OCMock-dummy.m b/Pods/Target Support Files/OCMock/OCMock-dummy.m new file mode 100644 index 000000000..7e5d15074 --- /dev/null +++ b/Pods/Target Support Files/OCMock/OCMock-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_OCMock : NSObject +@end +@implementation PodsDummy_OCMock +@end diff --git a/Pods/Target Support Files/OCMock/OCMock-prefix.pch b/Pods/Target Support Files/OCMock/OCMock-prefix.pch new file mode 100644 index 000000000..aa992a4ad --- /dev/null +++ b/Pods/Target Support Files/OCMock/OCMock-prefix.pch @@ -0,0 +1,4 @@ +#ifdef __OBJC__ +#import +#endif + diff --git a/Pods/Target Support Files/OCMock/OCMock-umbrella.h b/Pods/Target Support Files/OCMock/OCMock-umbrella.h new file mode 100644 index 000000000..d6cbe04c1 --- /dev/null +++ b/Pods/Target Support Files/OCMock/OCMock-umbrella.h @@ -0,0 +1,16 @@ +#import + +#import "OCMock.h" +#import "OCMockObject.h" +#import "OCMArg.h" +#import "OCMConstraint.h" +#import "OCMLocation.h" +#import "OCMMacroState.h" +#import "OCMRecorder.h" +#import "OCMStubRecorder.h" +#import "NSNotificationCenter+OCMAdditions.h" +#import "OCMFunctions.h" + +FOUNDATION_EXPORT double OCMockVersionNumber; +FOUNDATION_EXPORT const unsigned char OCMockVersionString[]; + diff --git a/Pods/Target Support Files/OCMock/OCMock.modulemap b/Pods/Target Support Files/OCMock/OCMock.modulemap new file mode 100644 index 000000000..8fe04aefe --- /dev/null +++ b/Pods/Target Support Files/OCMock/OCMock.modulemap @@ -0,0 +1,6 @@ +framework module OCMock { + umbrella header "OCMock-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/OCMock/OCMock.xcconfig b/Pods/Target Support Files/OCMock/OCMock.xcconfig new file mode 100644 index 000000000..b699424b7 --- /dev/null +++ b/Pods/Target Support Files/OCMock/OCMock.xcconfig @@ -0,0 +1,8 @@ +CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/OCMock +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" +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/Pods/Target Support Files/Pods-IGListKitTests/Info.plist b/Pods/Target Support Files/Pods-IGListKitTests/Info.plist new file mode 100644 index 000000000..2243fe6e2 --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/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/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-acknowledgements.markdown b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-acknowledgements.markdown new file mode 100644 index 000000000..96bd21480 --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-acknowledgements.markdown @@ -0,0 +1,184 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## OCMock + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-acknowledgements.plist b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-acknowledgements.plist new file mode 100644 index 000000000..6253eff6a --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-acknowledgements.plist @@ -0,0 +1,216 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + License + Apache 2.0 + Title + OCMock + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-dummy.m b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-dummy.m new file mode 100644 index 000000000..fbfe57689 --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_IGListKitTests : NSObject +@end +@implementation PodsDummy_Pods_IGListKitTests +@end diff --git a/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-frameworks.sh b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-frameworks.sh new file mode 100755 index 000000000..3e36e262a --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-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/OCMock/OCMock.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "$BUILT_PRODUCTS_DIR/OCMock/OCMock.framework" +fi diff --git a/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-resources.sh b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-resources.sh new file mode 100755 index 000000000..0a1561528 --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-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/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-umbrella.h b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-umbrella.h new file mode 100644 index 000000000..ade2d6bf2 --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests-umbrella.h @@ -0,0 +1,6 @@ +#import + + +FOUNDATION_EXPORT double Pods_IGListKitTestsVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_IGListKitTestsVersionString[]; + diff --git a/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.debug.xcconfig b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.debug.xcconfig new file mode 100644 index 000000000..690a73609 --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.debug.xcconfig @@ -0,0 +1,9 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/OCMock" +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/OCMock/OCMock.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "OCMock" +PODS_BUILD_DIR = $BUILD_DIR +PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT}/Pods diff --git a/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.modulemap b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.modulemap new file mode 100644 index 000000000..c616232af --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.modulemap @@ -0,0 +1,6 @@ +framework module Pods_IGListKitTests { + umbrella header "Pods-IGListKitTests-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.release.xcconfig b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.release.xcconfig new file mode 100644 index 000000000..690a73609 --- /dev/null +++ b/Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.release.xcconfig @@ -0,0 +1,9 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/OCMock" +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/OCMock/OCMock.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "OCMock" +PODS_BUILD_DIR = $BUILD_DIR +PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT}/Pods diff --git a/README.md b/README.md new file mode 100644 index 000000000..cefdb25a8 --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +

+ +

+ + + +------------------------ + +A data-driven `UICollectionView` framework for building fast and flexible lists. + + | Main Features +---------|--------------- +:no_good: | Never call `performBatchUpdates(_:, completion:)` or `reloadData()` again +:house: | Better architecture with reusable cells and components +:capital_abcd: | Create collections with multiple data types +:key: | Decoupled diffing algorithm +:white_check_mark: | Fully unit tested +:mag: | Customize your diffing behavior for your models +:iphone: | Simply `UICollectionView` at its core +:rocket: | Extendable API +:bird: | Written in Objective-C with full Swift interop support + +`IGListKit` is built and maintained by [Instagram engineering](https://engineering.instagram.com/), using the open source version for the Instagram app. + +## Installation + +The preferred installation method for `IGListKit` is with [Cocoapods](http://cocoapods.org). Simply add the following to your Podfile: + +```ruby +# Latest release of IGListKit +pod 'IGListKit' +``` + +You can also manually install the framework by dragging and dropping the `IGListKit.xcodeproj` into your workspace. + +`IGListKit` supports a minimum iOS version of 8.0. + +## Creating your first list + +After installing `IGListKit`, creating a new list is really simple. + +### Creating a section controller + +Creating a new section controller is very simple. You just subclass `IGListSectionController` and conform to the `IGListSectionType` protocol. Once you conform to `IGListSectionType`, the compiler will make sure you implement all of the required methods. + +Take a look at [LabelSectionController](https://github.com/Instagram/IGListKit/blob/master/Example/IGListKitExamples/SectionControllers/LabelSectionController.swift) for an example section controller that handles a `String` and configures a single cell with a `UILabel`. + +```swift +class LabelSectionController: IGListSectionController, IGListSectionType { + // ... +} +``` + +### Creating the UI + +After creating at least one section controller, you must create an `IGListCollectionView` and `IGListAdapter`. + +```swift +let layout = UICollectionViewFlowLayout() +let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: layout) + +let updater = IGListAdapterUpdater() +let adapter = IGListAdapter(updater: updater, viewController: self, workingRangeSize: 0) +adapter.collectionView = collectionView +``` + +> **Note:** This example is done within a `UIViewController` and uses both a stock `UICollectionViewFlowLayout` and `IGListAdapterUpdater`. You can use your own layout and updater if you need advanced features! + +### Connecting a data source + +The last step is the `IGListAdapter`'s data source and returning some data. + +```swift +func objectsForListAdapter(listAdapter: IGListAdapter) -> [IGListDiffable] { + // this can be anything! + return [ "Foo", "Bar", 42, "Biz" ] +} + +func listAdapter(listAdapter: IGListAdapter, + sectionControllerForObject(object: Any) -> IGListSectionController { + if let _ = object as? String { + return LabelSectionController() + } else { + return NumberSectionController() + } +} + +func emptyViewForListAdapter(listAdapter: IGListAdapter) -> UIView? { + return nil +} +``` + +You can return an array of _any_ type of data, as long as it conforms to `IGListDiffable`. We've included a [default implementation](https://github.com/Instagram/IGListKit/blob/master/Source/NSObject%2BIGListDiffable.m) for all objects, but adding your own implementation can unlock even better diffing. + +## Diffing + +`IGListKit` uses an algorithm adapted from a paper titled [A technique for isolating differences between files](http://dl.acm.org/citation.cfm?id=359467&dl=ACM&coll=DL) by Paul Heckel. This algorithm uses a technique known as the *longest common subsequence* to find a minimal diff between collections in linear time `O(n)`. It finds all **inserts**, **deletes**, **updates**, and **moves** between arrays of data. + +To add custom, diffable models, you need to conform to the `IGListDiffable` protocol and implement `diffIdentifier()` and `isEqual(_:)`. + +For an example, consider the following model: + +```swift +class User { + let primaryKey: Int + let name: String + // implementation, etc +} +``` + +The user's `primaryKey` uniquely identifies user data, and the `name` is just the value for that user. + +Consider the following two users: + +```swift +let shayne = User(primaryKey: 2, name: "Shayne") +let ann = User(primaryKey: 2, name: "Ann") +``` + +Both `shayne` and `ann` represent the same *unique* data because they share the same `primaryKey`, but they are not *equal* because their names are different. + +To represent this in `IGListKit`'s diffing, add and implement the `IGListDiffable` protocol: + +```swift +extension User: IGListDiffable { + func diffIdentifier() -> NSObjectProtocol { + return pk + } + + func isEqual(object: Any?) -> Bool { + if let object = object as? User { + return name == object.name + } + return false + } +} +``` + +The algorithm will skip updating two `User` objects that have the same `primaryKey` and `name`, even if they are different instances! You now avoid unecessary UI updates in the collection view even when providing new instances. + +> **Note:** Remember that `isEqual(_:)` should return `false` when you want to reload the cells in the corresponding section controller. + +### Diffing outside of IGListKit + +If you want to use the diffing algorithm outside of `IGListAdapter` and `UICollectionView`, you can! The diffing algorithm was built with the flexibility to be used with any models that conform to `IGListDiffable`. + +```swift +let result = IGListDiff(oldUsers, newUsers, .equality) +``` + +With this you have all of the deletes, reloads, moves, and inserts! There's even a function to generate `NSIndexPath` results. + +## Advanced Features + +### Working Range + +A *working range* is a distance before and after the visible bounds of the `UICollectionView` where section controllers within this bounds are notified of their entrance and exit. This concept lets your section controllers **prepare content** before they come on screen (e.g. download images). + +The `IGListAdapter` must be initialized with a range value in order to work. This value is a multiple of the visible height or width, depending on the scroll-direction. + +```swift +let adapter = IGListAdapter(updater: IGListAdapterUpdater(), + viewController: self, + workingRangeSize: 0.5) // 0.5x the visible size +``` + +![working-range](Resources/workingrange.png) + +You can set the weak `workingRangeDelegate` on an section controller to receive events. + +### Supplementary Views + +Adding supplementary views to section controllers is as simple as setting the weak `supplementaryViewSource` and implementing the `IGListSupplementaryViewSource` protocol. This protocol works nearly the same as returning and configuring cells. + +### Display Delegate + +Section controllers can set the weak `displayDelegate` delegate to an object, including `self`, to receive display events about a section controller and individual cells. + +### Custom Updaters + +The default `IGListAdapterUpdater` should handle any `UICollectionView` update that you need. However, if you find the functionality lacking, or want to perform updates in a very specific way, you can create an object that conforms to the `IGListUpdatingDelegate` protocol and initialize a new `IGListAdapter` with it. + +Check out the updater `IGListReloadDataUpdater` (used in unit tests) for an example. + +## Documentation + +You can find [the docs here](https://instagram.github.io/IGListKit). Documentation is generated with [jazzy](https://github.com/realm/jazzy) and hosted on [GitHub-Pages](https://pages.github.com). + +## Contributing + +Please see the [CONTRIBUTING](CONTRIBUTING.md) file for how to help out. At Instagram we sync the open source version of `IGListKit` almost daily, so we're always testing the latest changes. But that requires all changes be thoroughly tested follow our style guide. + +## License + +`IGListKit` is BSD-licensed. We also provide an additional patent grant. + +The files in the /Example directory are licensed under a separate license as specified in each file; documentation is licensed [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/). diff --git a/Resources/logo-animation.gif b/Resources/logo-animation.gif new file mode 100644 index 000000000..1bec02813 Binary files /dev/null and b/Resources/logo-animation.gif differ diff --git a/Resources/workingrange.png b/Resources/workingrange.png new file mode 100644 index 000000000..8321bf8af Binary files /dev/null and b/Resources/workingrange.png differ diff --git a/Source/IGListAdapter.h b/Source/IGListAdapter.h new file mode 100644 index 000000000..79f50eec2 --- /dev/null +++ b/Source/IGListAdapter.h @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import +#import +#import +#import +#import +#import + +@protocol IGListUpdatingDelegate; + +@class IGListSectionController; + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^IGListUpdaterCompletion)(BOOL finished); + +/** + IGListAdapter objects provide an abstraction for feeds of objects in a UICollectionView by breaking each object into + individual sections, called "section controllers". These controllers (objects conforming to IGListSectionType) act as a + data source and delegate for each section. + + Feed implementations must act as the data source for an IGListAdapter in order to drive the objects and section + controllers in a collection view. + */ +IGLK_SUBCLASSING_RESTRICTED +@interface IGListAdapter : NSObject + +/** + The view controller that houses the adapter. + */ +@property (nonatomic, nullable, weak) UIViewController *viewController; + +/** + The collection view used with the adapter. + */ +@property (nonatomic, nullable, weak) IGListCollectionView *collectionView; + +/** + The object that acts as the data source for the list adapter. + */ +@property (nonatomic, nullable, weak) id dataSource; + +/** + The object that receives top-level events for section controllers. + */ +@property (nonatomic, nullable, weak) id delegate; + +/** + The object that receives UICollectionViewDelegate events. + + @discussion This object /will not/ receive UIScrollViewDelegate events. Instead use scrollViewDelegate. + */ +@property (nonatomic, nullable, weak) id collectionViewDelegate; + +/** + The object that receives UIScrollViewDelegate events. + */ +@property (nonatomic, nullable, weak) id scrollViewDelegate; + +/** + A bitmask of experiments to conduct on the adapter. + */ +@property (nonatomic, assign) IGListExperiment experiments; + +/** + Initialize a new IGListAdapter object with a collection view, data source, and updating delegate. + + @param updatingDelegate An object that manages updates to the UICollectionView. + @param viewController The view controller that will house the adapter. + @param workingRangeSize The number of objects before and after the viewport to consider within the working range. + + @return A new IGListAdapter object. + + @discussion The working range is the number of objects beyond the visible objects (plus and minus) that should be + notified when they are close to being visible. For instance, if you have 3 objects on screen and a working range of 2, + the previous and succeeding 2 objects will be notified that they are within the working range. As you scroll the list + the range is updated as objects enter and exit the working range. + + To opt out of using the working range, you can provide a value of 0. + */ +- (instancetype)initWithUpdater:(id )updatingDelegate + viewController:(nullable UIViewController *)viewController + workingRangeSize:(NSUInteger)workingRangeSize NS_DESIGNATED_INITIALIZER; + +/** + Perform an update from the previous state of the data source. This is analagous to calling + -[UICollectionView performBatchUpdates:completion:]. + + @param animated A flag indicating if the transition should be animated. + @param completion A block executed when the update completes. + */ +- (void)performUpdatesAnimated:(BOOL)animated completion:(nullable IGListUpdaterCompletion)completion; + +/** + Perform an immediate reload of the data in the data source, discarding the old objectss. + + @param completion A block executed when the reload completes. + */ +- (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion; + +/** + Reload the infra for specific objectss only. + + @param objects The objects to reload. + */ +- (void)reloadObjects:(NSArray *)objects; + +/** + Query the section index of a list. Constant time lookup. + + @param sectionController A list object. + + @return The section index of the list or NSNotFound. + */ +- (NSUInteger)sectionForSectionController:(IGListSectionController *)sectionController; + +/** + Fetch an section controller for an object in the feed. Constant time lookup. + + @param object An object from the data source. + + @return An section controller or nil. + + @see -[IGListAdapterDataSource listAdapter:sectionControllerForObject:] + */ +- (__kindof IGListSectionController * _Nullable)sectionControllerForObject:(id)object; + +/** + Fetch the object corresponding to a section in the feed. Constant time lookup. + + @param section A section in the feed. + + @return An object or nil. + */ +- (nullable id)objectAtSection:(NSUInteger)section; + +/** + Fetch the section corresponding to an object in the feed. Constant time lookup. + + @param object An object in the feed + + @return A section index if found or NSNotFound. + */ +- (NSUInteger)sectionForObject:(id)object; + +/** + A copy of all the objects currently powering the adapter. + + @return An array of objects. + */ +- (NSArray *)objects; + +/** + An unordered array of the currently visible section controllers. + + @return An array of section controllers. + */ +- (NSArray *> *)visibleSectionControllers; + +/** + Scroll to an object in the list adapter. + + @param object The object to scroll to. + @param supplementaryKinds The types of supplementary views in the section. + @param scrollDirection A flag indicating the direction to scroll. + @param animated A flag indicating if the transition should be animated. + */ +- (void)scrollToObject:(id)object + supplementaryKinds:(nullable NSArray *)supplementaryKinds + scrollDirection:(UICollectionViewScrollDirection)scrollDirection + animated:(BOOL)animated; + +/** + Query the size of a cell at the specified index path. + + @param indexPath The index path of the cell. + + @return The size of the cell. + */ +- (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath; + +/** + Query the size of a supplementary view in the list at the specified index path. + + @param elementKind The kind of supplementary view. + @param indexPath The index path of the supplementary view. + + @return The size of the supplementary view. + */ +- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind + atIndexPath:(NSIndexPath *)indexPath; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m new file mode 100644 index 000000000..36b219563 --- /dev/null +++ b/Source/IGListAdapter.m @@ -0,0 +1,858 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListAdapterInternal.h" + +#import +#import +#import +#import + +#import "IGListSectionControllerInternal.h" +#import "NSIndexSet+PrettyDescription.h" + +@implementation IGListAdapter { + NSMapTable *> *_cellSectionControllerMap; + BOOL _isDequeuingCell; +} + +- (void)dealloc { + // on iOS 9 setting the dataSource has side effects that can invalidate the layout and seg fault + if ([[[UIDevice currentDevice] systemVersion] floatValue] < 9.0) { + // properties are assign for )updatingDelegate + viewController:(UIViewController *)viewController + workingRangeSize:(NSUInteger)workingRangeSize { + IGAssertMainThread(); + IGParameterAssert(updatingDelegate); + + if (self = [super init]) { + NSPointerFunctions *keyFunctions = [updatingDelegate objectLookupPointerFunctions]; + NSPointerFunctions *valueFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory]; + NSMapTable *table = [[NSMapTable alloc] initWithKeyPointerFunctions:keyFunctions valuePointerFunctions:valueFunctions capacity:0]; + _sectionMap = [[IGListSectionMap alloc] initWithMapTable:table]; + + _displayHandler = [[IGListDisplayHandler alloc] init]; + _workingRangeHandler = [[IGListWorkingRangeHandler alloc] initWithWorkingRangeSize:workingRangeSize]; + + _cellSectionControllerMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality | NSMapTableStrongMemory + valueOptions:NSMapTableStrongMemory]; + + _updatingDelegate = updatingDelegate; + _viewController = viewController; + } + return self; +} + +- (IGListCollectionView *)collectionView { + return (IGListCollectionView *)_collectionView; +} + +- (void)setCollectionView:(IGListCollectionView *)collectionView { + IGAssertMainThread(); + IGParameterAssert([collectionView isKindOfClass:[IGListCollectionView class]]); + + // if collection view has been used by a different list adapter, treat it as if we were using a new collection view + // this happens when embedding a IGListCollectionView inside a UICollectionViewCell that is reused + if (_collectionView != collectionView || _collectionView.dataSource != self) { + // dump old registered section controllers in the case that we are changing collection views or setting for + // the first time + _registeredCellClasses = [NSMutableSet new]; + _registeredSupplementaryViewIdentifiers = [NSMutableSet new]; + + _collectionView = collectionView; + _collectionView.dataSource = self; + + [self updateCollectionViewDelegate]; + [self updateAfterPublicSettingsChange]; + } +} + +- (void)setDataSource:(id)dataSource { + if (_dataSource != dataSource) { + _dataSource = dataSource; + [self updateAfterPublicSettingsChange]; + } +} + +// reset and configure the delegate proxy whenever this property is set +- (void)setCollectionViewDelegate:(id)collectionViewDelegate { + IGAssertMainThread(); + IGAssert(![collectionViewDelegate conformsToProtocol:@protocol(UICollectionViewDelegateFlowLayout)], + @"UICollectionViewDelegateFlowLayout conformance is automatically handled by IGListAdapter."); + + if (_collectionViewDelegate != collectionViewDelegate) { + _collectionViewDelegate = collectionViewDelegate; + [self createProxyAndUpdateCollectionViewDelegate]; + } +} + +- (void)setScrollViewDelegate:(id)scrollViewDelegate { + IGAssertMainThread(); + + if (_scrollViewDelegate != scrollViewDelegate) { + _scrollViewDelegate = scrollViewDelegate; + [self createProxyAndUpdateCollectionViewDelegate]; + } +} + +- (void)updateAfterPublicSettingsChange { + if (_collectionView != nil && _dataSource != nil) { + [self updateObjects:[[_dataSource objectsForListAdapter:self] copy]]; + + if (IGListExperimentEnabled(self.experiments, IGListExperimentUICVReloadedInSetter)) { + [_collectionView reloadData]; + } + } +} + +- (void)createProxyAndUpdateCollectionViewDelegate { + // there is a known bug with accessibility and using an NSProxy as the delegate that will cause EXC_BAD_ACCESS + // when voiceover is enabled. it will hold an unsafe ref to the delegate + _collectionView.delegate = nil; + + self.delegateProxy = [[IGListAdapterProxy alloc] initWithCollectionViewTarget:_collectionViewDelegate + scrollViewTarget:_scrollViewDelegate + interceptor:self]; + [self updateCollectionViewDelegate]; +} + +- (void)updateCollectionViewDelegate { + // set up the delegate to the proxy so the adapter can intercept events + // default to the adapter simply being the delegate + _collectionView.delegate = (id)self.delegateProxy ?: self; +} + + +#pragma mark - Scrolling + +- (void)scrollToObject:(id)object + supplementaryKinds:(NSArray *)supplementaryKinds + scrollDirection:(UICollectionViewScrollDirection)scrollDirection + animated:(BOOL)animated { + IGAssertMainThread(); + IGParameterAssert(object != nil); + + const NSUInteger section = [self sectionForObject:object]; + if (section == NSNotFound) { + return; + } + + UICollectionView *collectionView = self.collectionView; + const NSUInteger numberOfItems = [collectionView numberOfItemsInSection:section]; + if (numberOfItems == 0) { + return; + } + + // force layout before continuing + // this method is typcially called before pushing a view controller + // thus, before the layout process has actually happened + [collectionView layoutIfNeeded]; + + // collect the layout attributes for the cell and supplementary views for the first index + // this will break if there are supplementary views beyond item 0 + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; + NSArray *attributes = [self layoutAttributesForIndexPath:indexPath supplementaryKinds:supplementaryKinds]; + + CGFloat offset = 0.0; + for (UICollectionViewLayoutAttributes *attribute in attributes) { + const CGRect frame = attribute.frame; + CGFloat origin; + switch (scrollDirection) { + case UICollectionViewScrollDirectionHorizontal: + origin = CGRectGetMinX(frame); + break; + case UICollectionViewScrollDirectionVertical: + origin = CGRectGetMinY(frame); + break; + } + + // find the minimum origin value of all the layout attributes + if (attribute == attributes.firstObject || origin < offset) { + offset = origin; + } + } + + const UIEdgeInsets contentInset = collectionView.contentInset; + CGPoint contentOffset = collectionView.contentOffset; + switch (scrollDirection) { + case UICollectionViewScrollDirectionHorizontal: + contentOffset.x = offset - contentInset.left; + break; + case UICollectionViewScrollDirectionVertical: + contentOffset.y = offset - contentInset.top; + break; + } + + [collectionView setContentOffset:contentOffset animated:animated]; +} + + +#pragma mark - Editing + +- (void)performUpdatesAnimated:(BOOL)animated completion:(IGListUpdaterCompletion)completion { + IGAssertMainThread(); + + id dataSource = self.dataSource; + UICollectionView *collectionView = self.collectionView; + if (dataSource == nil || collectionView == nil) { + if (completion) { + completion(NO); + } + return; + } + + NSArray *fromObjects = [self.sectionMap.objects copy]; + NSArray *newItems = [[dataSource objectsForListAdapter:self] copy]; + + __weak __typeof__(self) weakSelf = self; + [self.updatingDelegate performUpdateWithCollectionView:collectionView + fromObjects:fromObjects + toObjects:newItems + animated:animated + objectTransitionBlock:^(NSArray *toObjects) { + // temporarily capture the item map that we are transitioning from in case + // there are any item deletes at the same + weakSelf.previoussectionMap = [weakSelf.sectionMap copy]; + + [weakSelf updateObjects:toObjects]; + } completion:^(BOOL finished) { + // release the previous items + weakSelf.previoussectionMap = nil; + + if (completion) { + completion(finished); + } + }]; +} + +- (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion { + IGAssertMainThread(); + + id dataSource = self.dataSource; + UICollectionView *collectionView = self.collectionView; + if (dataSource == nil || collectionView == nil) { + if (completion) { + completion(NO); + return; + } + } + + NSArray *newItems = [[dataSource objectsForListAdapter:self] copy]; + + __weak __typeof__(self) weakSelf = self; + [self.updatingDelegate reloadDataWithCollectionView:collectionView reloadUpdateBlock:^{ + // purge all section controllers from the item map so that they are regenerated + [weakSelf.sectionMap reset]; + [weakSelf updateObjects:newItems]; + } completion:completion]; +} + +- (void)reloadObjects:(NSArray *)objects { + IGAssertMainThread(); + IGParameterAssert(objects); + + NSMutableIndexSet *sections = [[NSMutableIndexSet alloc] init]; + + // use the item map based on whether or not we're in an update block + IGListSectionMap *map = [self sectionMapAdjustForUpdateBlock:YES]; + + for (id object in objects) { + // look up the item using the map's lookup function. might not be the same item + NSUInteger section = [map sectionForObject:object]; + IGAssert(section != NSNotFound, @"Did not find a section for item %@", object); + [sections addIndex:section]; + + // reverse lookup the item using the section. if the pointer has changed the trigger update events and swap items + if (object != [map objectForSection:section]) { + [map updateObject:object]; + [[map sectionControllerForSection:section] didUpdateToObject:object]; + } + } + + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Reloading adapter without a collection view"); + + [self.updatingDelegate reloadCollectionView:collectionView sections:sections]; +} + + +#pragma mark - List Items & Sections + +- (NSUInteger)sectionForSectionController:(IGListSectionController *)sectionController { + IGAssertMainThread(); + IGParameterAssert(sectionController != nil); + + return [self.sectionMap sectionForSectionController:sectionController]; +} + +- (id )sectionControllerForObject:(id)object { + IGAssertMainThread(); + IGParameterAssert(object != nil); + + return [self.sectionMap sectionControllerForObject:object]; +} + +- (id)objectAtSection:(NSUInteger)section { + IGAssertMainThread(); + + return [self.sectionMap objectForSection:section]; +} + +- (NSUInteger)sectionForObject:(id)item { + IGAssertMainThread(); + IGParameterAssert(item != nil); + + return [self.sectionMap sectionForObject:item]; +} + +- (NSArray *)objects { + IGAssertMainThread(); + + return [self.sectionMap.objects copy]; +} + +- (id)supplementaryViewSourceAtIndexPath:(NSIndexPath *)indexPath { + IGListSectionController *sectionController = [self.sectionMap sectionControllerForSection:indexPath.section]; + return [sectionController supplementaryViewSource]; +} + +- (NSArray *> *)visibleSectionControllers { + IGAssertMainThread(); + NSArray *visibleCells = [self.collectionView visibleCells]; + NSMutableSet *visibleSectionControllers = [NSMutableSet new]; + for (UICollectionViewCell *cell in visibleCells) { + IGListSectionController *sectionController = [self sectionControllerForCell:cell]; + IGAssert(sectionController != nil, @"Section controller nil for cell %@", cell); + if (sectionController) { + [visibleSectionControllers addObject:sectionController]; + } + } + return [visibleSectionControllers allObjects]; +} + + +#pragma mark - Layout + +- (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + IGAssertMainThread(); + + IGListSectionController *sectionController = [self.sectionMap sectionControllerForSection:indexPath.section]; + return [sectionController sizeForItemAtIndex:indexPath.item]; +} + +- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { + IGAssertMainThread(); + id supplementaryViewSource = [self supplementaryViewSourceAtIndexPath:indexPath]; + if ([[supplementaryViewSource supportedElementKinds] containsObject:elementKind]) { + return [supplementaryViewSource sizeForSupplementaryViewOfKind:elementKind atIndex:indexPath.item]; + } + return CGSizeZero; +} + + +#pragma mark - Private API + +// this method is what updates the "source of truth" +// this should only be called just before the collection view is updated +- (void)updateObjects:(NSArray *)objects { +#if DEBUG + for (id object in objects) { + IGAssert([object isEqual:object], @"Object instance %@ not equal to itself. This will break infra map tables.", object); + } +#endif + + NSMutableArray *> *sectionControllers = [[NSMutableArray alloc] init]; + IGListSectionMap *map = self.sectionMap; + + // collect items that have changed since the last update + NSMutableSet *updatedObjects = [NSMutableSet new]; + + // push the view controller and collection context into a local thread container so they are available on init + // for IGListSectionController subclasses after calling [super init] + IGListSectionControllerPushThread(self.viewController, self); + + for (id object in objects) { + // infra checks to see if a controller exists + IGListSectionController *sectionController = [map sectionControllerForObject:object]; + + // if not, query the data source for a new one + if (sectionController == nil) { + sectionController = [self.dataSource listAdapter:self sectionControllerForObject:object]; + } + + IGAssert(sectionController != nil, @"Data source <%@> cannot return a nil section controller.", self.dataSource); + if (sectionController == nil) { + break; + } + + // in case the section controller was created outside of -listAdapter:sectionControllerForObject: + sectionController.collectionContext = self; + sectionController.viewController = self.viewController; + + // check if the item has changed instances or is new + const NSUInteger oldSection = [map sectionForObject:object]; + if (oldSection == NSNotFound || [map objectForSection:oldSection] != object) { + [updatedObjects addObject:object]; + } + + [sectionControllers addObject:sectionController]; + } + + // clear the view controller and collection context + IGListSectionControllerPopThread(); + + [map updateWithObjects:objects sectionControllers:[sectionControllers copy]]; + + // now that the maps have been created and contexts are assigned, we consider the section controller "fully loaded" + for (id object in updatedObjects) { + [[map sectionControllerForObject:object] didUpdateToObject:object]; + } + + NSUInteger itemCount = 0; + for (IGListSectionController *sectionController in sectionControllers) { + itemCount += [sectionController numberOfItems]; + } + + [self updateBackgroundViewWithItemCount:itemCount]; +} + +- (void)updateBackgroundViewWithItemCount:(NSUInteger)itemCount { + UIView *backgroundView = [self.dataSource emptyViewForListAdapter:self]; + // don't do anything if the client is using the same view + if (backgroundView != _collectionView.backgroundView) { + // collection view will just stack the background views underneath each other if we do not remove the previous + // one first. also fine if it is nil + [_collectionView.backgroundView removeFromSuperview]; + _collectionView.backgroundView = backgroundView; + } + _collectionView.backgroundView.hidden = itemCount > 0; +} + +// use the string representation of a reusable view class when registering with a UICollectionView +- (NSString *)reusableViewIdentifierForClass:(Class)viewClass { + return NSStringFromClass(viewClass); +} + +- (NSString *)reusableViewIdentifierForClass:(Class)viewClass elementKind:(NSString *)elementKind { + return [elementKind stringByAppendingString:NSStringFromClass(viewClass)]; +} + +- (IGListSectionMap *)sectionMapAdjustForUpdateBlock:(BOOL)adjustForUpdateBlock { + // if we are inside an update block, we may have to use the /previous/ item map for some operations + if (adjustForUpdateBlock && self.isInUpdateBlock && self.previoussectionMap != nil) { + return self.previoussectionMap; + } else { + return self.sectionMap; + } +} + +- (NSArray *)indexPathsFromSectionController:(IGListSectionController *)sectionController + indexes:(NSIndexSet *)indexes + adjustForUpdateBlock:(BOOL)adjustForUpdateBlock { + NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; + + IGListSectionMap *map = [self sectionMapAdjustForUpdateBlock:adjustForUpdateBlock]; + + const NSUInteger section = [map sectionForSectionController:sectionController]; + if (section != NSNotFound) { + [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]]; + }]; + } + return [indexPaths copy]; +} + +- (NSIndexPath *)indexPathForSectionController:(IGListSectionController *)controller index:(NSInteger)index { + const NSUInteger section = [self.sectionMap sectionForSectionController:controller]; + if (section == NSNotFound) { + return nil; + } else { + return [NSIndexPath indexPathForItem:index inSection:section]; + } +} + +- (NSArray *)layoutAttributesForIndexPath:(NSIndexPath *)indexPath + supplementaryKinds:(NSArray *)supplementaryKinds { + UICollectionViewLayout *layout = self.collectionView.collectionViewLayout; + NSMutableArray *attributes = [[NSMutableArray alloc] init]; + + UICollectionViewLayoutAttributes *cellAttributes = [layout layoutAttributesForItemAtIndexPath:indexPath]; + if (cellAttributes) { + [attributes addObject:cellAttributes]; + } + + for (NSString *kind in supplementaryKinds) { + UICollectionViewLayoutAttributes *supplementaryAttributes = [layout layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; + if (supplementaryAttributes) { + [attributes addObject:supplementaryAttributes]; + } + } + + return [attributes copy]; +} + +- (void)mapCell:(UICollectionViewCell *)cell toSectionController:(IGListSectionController *)sectionController { + IGAssertMainThread(); + IGParameterAssert(cell != nil); + IGParameterAssert(sectionController != nil); + [_cellSectionControllerMap setObject:sectionController forKey:cell]; +} + +- (nullable IGListSectionController *)sectionControllerForCell:(UICollectionViewCell *)cell { + IGAssertMainThread(); + return [_cellSectionControllerMap objectForKey:cell]; +} + +- (void)removeMapForCell:(UICollectionViewCell *)cell { + IGAssertMainThread(); + [_cellSectionControllerMap removeObjectForKey:cell]; +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return self.sectionMap.objects.count; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + IGListSectionController * sectionController = [self.sectionMap sectionControllerForSection:section]; + IGAssert(sectionController != nil, @"Nil section controller for section %zi for item %@. Check your -diffIdentifier and -isEqual: implementations.", + section, [self.sectionMap objectForSection:section]); + const NSInteger numberOfItems = [sectionController numberOfItems]; + IGAssert(numberOfItems >= 0, @"Cannot return negative number of items %zi for section controller %@.", numberOfItems, sectionController); + return numberOfItems; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + IGListSectionController *sectionController = [self.sectionMap sectionControllerForSection:indexPath.section]; + + // flag that a cell is being dequeued in case it tries to access a cell in the process + _isDequeuingCell = YES; + UICollectionViewCell *cell = [sectionController cellForItemAtIndex:indexPath.item]; + _isDequeuingCell = NO; + + IGAssert(cell != nil, @"Returned a nil cell at indexPath <%@> from section controller: <%@>", indexPath, sectionController); + + // associate the section controller with the cell so that we know which section controller is using it + [self mapCell:cell toSectionController:sectionController]; + + return cell; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + IGListSectionController *sectionController = [self.sectionMap sectionControllerForSection:indexPath.section]; + id supplementarySource = [sectionController supplementaryViewSource]; + UICollectionReusableView *view = [supplementarySource viewForSupplementaryElementOfKind:kind atIndex:indexPath.item]; + IGAssert(view != nil, @"Returned a nil supplementary view at indexPath <%@> from section controller: <%@>, supplementary source: <%@>", indexPath, sectionController, supplementarySource); + return view; +} + + +#pragma mark - UICollectionViewDelegate + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + // forward this method to the delegate b/c this implementation will steal the message from the proxy + id collectionViewDelegate = self.collectionViewDelegate; + if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]) { + [collectionViewDelegate collectionView:collectionView didSelectItemAtIndexPath:indexPath]; + } + + IGListSectionController * sectionController = [self.sectionMap sectionControllerForSection:indexPath.section]; + [sectionController didSelectItemAtIndex:indexPath.item]; +} + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + // forward this method to the delegate b/c this implementation will steal the message from the proxy + id collectionViewDelegate = self.collectionViewDelegate; + if ([collectionViewDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]) { + [collectionViewDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; + } + + IGListSectionController *sectionController = [self sectionControllerForCell:cell]; + // if the section controller relationship was destroyed, reconnect it + // this happens with iOS 10 UICollectionView display range changes + if (sectionController == nil) { + sectionController = [self.sectionMap sectionControllerForSection:indexPath.section]; + [self mapCell:cell toSectionController:sectionController]; + } + + id object = [self.sectionMap objectForSection:indexPath.section]; + [self.displayHandler willDisplayCell:cell forListAdapter:self sectionController:sectionController object:object indexPath:indexPath]; + [self.workingRangeHandler willDisplayItemAtIndexPath:indexPath forListAdapter:self]; +} + +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + // forward this method to the delegate b/c this implementation will steal the message from the proxy + id collectionViewDelegate = self.collectionViewDelegate; + if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)]) { + [collectionViewDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; + } + + IGListSectionController *sectionController = [self sectionControllerForCell:cell]; + [self.displayHandler didEndDisplayingCell:cell forListAdapter:self sectionController:sectionController indexPath:indexPath]; + [self.workingRangeHandler didEndDisplayingItemAtIndexPath:indexPath forListAdapter:self]; + + // break the association between the cell and the section controller + [self removeMapForCell:cell]; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + // forward this method to the delegate b/c this implementation will steal the message from the proxy + id scrollViewDelegate = self.scrollViewDelegate; + if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { + [scrollViewDelegate scrollViewDidScroll:scrollView]; + } + NSArray *> *visibleSectionControllers = [self visibleSectionControllers]; + for (IGListSectionController *sectionController in visibleSectionControllers) { + [[sectionController scrollDelegate] listAdapter:self didScrollSectionController:sectionController]; + } +} + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + // forward this method to the delegate b/c this implementation will steal the message from the proxy + id scrollViewDelegate = self.scrollViewDelegate; + if ([scrollViewDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) { + [scrollViewDelegate scrollViewWillBeginDragging:scrollView]; + } + NSArray *> *visibleSectionControllers = [self visibleSectionControllers]; + for (IGListSectionController *sectionController in visibleSectionControllers) { + [[sectionController scrollDelegate] listAdapter:self willBeginDraggingSectionController:sectionController]; + } +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + // forward this method to the delegate b/c this implementation will steal the message from the proxy + id scrollViewDelegate = self.scrollViewDelegate; + if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) { + [scrollViewDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; + } + NSArray *> *visibleSectionControllers = [self visibleSectionControllers]; + for (IGListSectionController *sectionController in visibleSectionControllers) { + [[sectionController scrollDelegate] listAdapter:self didEndDraggingSectionController:sectionController willDecelerate:decelerate]; + } +} + + +#pragma mark - IGListCollectionContext + +- (CGSize)containerSize { + return UIEdgeInsetsInsetRect(self.collectionView.bounds, self.collectionView.contentInset).size; +} + +- (NSUInteger)indexForCell:(UICollectionViewCell *)cell sectionController:(nonnull IGListSectionController *)sectionController { + IGAssertMainThread(); + IGParameterAssert(cell != nil); + IGParameterAssert(sectionController != nil); + NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; + IGAssert(indexPath.section == [self sectionForSectionController:sectionController], + @"Requesting a cell from another section controller is not allowed."); + return indexPath != nil ? indexPath.item : NSNotFound; +} + +- (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index + sectionController:(IGListSectionController *)sectionController { + IGAssertMainThread(); + IGParameterAssert(sectionController != nil); + + // if this is accessed while a cell is being dequeued, just return nil + if (_isDequeuingCell) { + return nil; + } + + NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index]; + // prevent querying the collection view if it isn't fully reloaded yet for the current data set + if (indexPath != nil + && indexPath.section < [self.collectionView numberOfSections]) { + // only return a cell if it belongs to the section controller + // this association is created in -collectionView:cellForItemAtIndexPath: + UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; + if ([self sectionControllerForCell:cell] == sectionController) { + return cell; + } + } + return nil; +} + +- (NSArray *)visibleCellsForSectionController:(IGListSectionController *)sectionController { + NSMutableArray *cells = [NSMutableArray new]; + UICollectionView *collectionView = self.collectionView; + NSArray *visibleCells = [collectionView visibleCells]; + const NSUInteger section = [self sectionForSectionController:sectionController]; + for (UICollectionViewCell *cell in visibleCells) { + if ([collectionView indexPathForCell:cell].section == section) { + [cells addObject:cell]; + } + } + return [cells copy]; +} + +- (void)deselectItemAtIndex:(NSInteger)index + sectionController:(IGListSectionController *)sectionController + animated:(BOOL)animated { + IGAssertMainThread(); + IGParameterAssert(sectionController != nil); + NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index]; + [self.collectionView deselectItemAtIndexPath:indexPath animated:animated]; +} + +- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index { + IGAssertMainThread(); + IGParameterAssert(sectionController != nil); + IGParameterAssert(cellClass != nil); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Reloading adapter without a collection view."); + NSString *identifier = [self reusableViewIdentifierForClass:cellClass]; + NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index]; + if (![self.registeredCellClasses containsObject:cellClass]) { + [self.registeredCellClasses addObject:cellClass]; + [collectionView registerClass:cellClass forCellWithReuseIdentifier:identifier]; + } + return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath]; +} + +- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind + forSectionController:(IGListSectionController *)sectionController + class:(Class)viewClass + atIndex:(NSInteger)index { + IGAssertMainThread(); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Reloading adapter without a collection view."); + NSString *identifier = [self reusableViewIdentifierForClass:viewClass elementKind:elementKind]; + NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index]; + if (![self.registeredSupplementaryViewIdentifiers containsObject:identifier]) { + [self.registeredSupplementaryViewIdentifiers addObject:identifier]; + [collectionView registerClass:viewClass forSupplementaryViewOfKind:elementKind withReuseIdentifier:identifier]; + } + return [collectionView dequeueReusableSupplementaryViewOfKind:elementKind withReuseIdentifier:identifier forIndexPath:indexPath]; +} + +- (void)reloadInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { + IGAssertMainThread(); + IGParameterAssert(indexes != nil); + IGParameterAssert(sectionController != nil); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Reloading from %@ without a collection view.", sectionController); + + if (indexes.count == 0) { + return; + } + + NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes adjustForUpdateBlock:YES]; + [self.updatingDelegate reloadItemsInCollectionView:collectionView indexPaths:indexPaths]; +} + +- (void)insertInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { + IGAssertMainThread(); + IGParameterAssert(indexes != nil); + IGParameterAssert(sectionController != nil); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Inserting items from %@ without a collection view.", sectionController); + + if (indexes.count == 0) { + return; + } + + NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes adjustForUpdateBlock:NO]; + [self.updatingDelegate insertItemsIntoCollectionView:collectionView indexPaths:indexPaths]; +} + +- (void)deleteInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { + IGAssertMainThread(); + IGParameterAssert(indexes != nil); + IGParameterAssert(sectionController != nil); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Deleting items from %@ without a collection view.", sectionController); + + if (indexes.count == 0) { + return; + } + + NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes adjustForUpdateBlock:YES]; + [self.updatingDelegate deleteItemsFromCollectionView:collectionView indexPaths:indexPaths]; +} + +- (void)reloadSectionController:(IGListSectionController *)sectionController { + IGAssertMainThread(); + IGParameterAssert(sectionController != nil); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Reloading items from %@ without a collection view.", sectionController); + + IGListSectionMap *map = [self sectionMapAdjustForUpdateBlock:YES]; + const NSInteger section = [map sectionForSectionController:sectionController]; + if (section == NSNotFound) { + return; + } + + NSIndexSet *sections = [NSIndexSet indexSetWithIndex:section]; + [self.updatingDelegate reloadCollectionView:collectionView sections:sections]; +} + +- (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion { + IGAssertMainThread(); + IGParameterAssert(updates != nil); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Performing batch updates without a collection view."); + + __weak __typeof__(self) weakSelf = self; + [self.updatingDelegate performUpdateWithCollectionView:collectionView animated:animated itemUpdates:^{ + weakSelf.isInUpdateBlock = YES; + updates(); + weakSelf.isInUpdateBlock = NO; + } completion:completion]; +} + + +#pragma mark - UICollectionViewDelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); + return [self sizeForItemAtIndexPath:indexPath]; +} + +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); + return [[self.sectionMap sectionControllerForSection:section] inset]; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { + IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); + return [[self.sectionMap sectionControllerForSection:section] minimumLineSpacing]; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { + IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); + return [[self.sectionMap sectionControllerForSection:section] minimumInteritemSpacing]; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { + IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; + return [self sizeForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { + IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; + return [self sizeForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:indexPath]; +} + +@end diff --git a/Source/IGListAdapterDataSource.h b/Source/IGListAdapterDataSource.h new file mode 100644 index 000000000..c28207bdc --- /dev/null +++ b/Source/IGListAdapterDataSource.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@class IGListAdapter; +@class IGListSectionController; + +@protocol IGListSectionType; + +NS_ASSUME_NONNULL_BEGIN + +/** + Implement this protocol to provide data to power an IGListAdapter feed. + */ +@protocol IGListAdapterDataSource + +/** + Asks the data source for an array of objects for each list in your feed. + + @param listAdapter The list adapter requesting this information. + + @return An array of objects for the feed. + */ +- (NSArray> *)objectsForListAdapter:(IGListAdapter *)listAdapter; + +/** + Asks the data source for a section controller for the specified data object. + + @param listAdapter The list adapter requesting this information. + @param object An object in the feed, provided in -objectsForListAdapter:. + + @return An IGListSectionType conforming object that can be displayed in the feed. + + @discussion New section controllers should be initialized here for objects when asked. You may pass any other data to + the section controller at this time. + + Section controllers are initialized for all objects whenever the IGListAdapter is created, updated, or reloaded. + Section controllers are reused when objects are moved or updated. Maintaining the -[IGListDiffable diffIdentifier] + gauruntees this. + */ +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object; + +/** + Asks the data source for a view to use as the collection view background when there are no objects. + + @param listAdapter The list adapter requesting this information. + + @return A view to use as the collection view background, or nil if you don't want a background view. + + @discussion This method is called every time the list adapter is updated. You are free to return new views every time, + but for performance reasons you may want to retain your own view and return it here. The infra is only responsible for + adding the background view and maintaining its visibility. + */ +- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListAdapterDelegate.h b/Source/IGListAdapterDelegate.h new file mode 100644 index 000000000..5531b14c1 --- /dev/null +++ b/Source/IGListAdapterDelegate.h @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class IGListAdapter; + +@protocol IGListAdapterDelegate + +/** + Notifies the delegate that a list object is about to be displayed + + @param listAdapter The list adapter sending this information. + @param object The object that will display. + @param index The index of the object in the list. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter willDisplayObject:(id)object atIndex:(NSInteger)index; + +/** + Notifies the delegate that a list item is no longer being displayed + + @param listAdapter The list adapter sending this information. + @param object The object that ended display. + @param index The index of the item/object in the list. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingObject:(id)object atIndex:(NSInteger)index; + +@end diff --git a/Source/IGListAdapterUpdater.h b/Source/IGListAdapterUpdater.h new file mode 100644 index 000000000..136d29ca2 --- /dev/null +++ b/Source/IGListAdapterUpdater.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + This is an out-of-box upater for IGListAdapters. It conforms to IGListUpdatingDelegate and does re-entrant, coalesced + updating on a UICollectionView. + + It also uses IGDiffKit (a least-minimal diff) for calculating UI updates when IGListAdapter calls + -performUpdateWithCollectionView:fromObjects:toObjects:completion:. + */ +IGLK_SUBCLASSING_RESTRICTED +@interface IGListAdapterUpdater : NSObject + +/** + A delegate that receives events with data on the performance of a transition. + */ +@property (nonatomic, weak) id delegate; + +/** + A flag indicating if a move should be treated as a delete+insert. + */ +@property (nonatomic, assign) BOOL movesAsDeletesInserts; + +/** + A bitmask of experiments to conduct on the updater. + */ +@property (nonatomic, assign) IGListExperiment experiments; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListAdapterUpdater.m b/Source/IGListAdapterUpdater.m new file mode 100644 index 000000000..699b51234 --- /dev/null +++ b/Source/IGListAdapterUpdater.m @@ -0,0 +1,520 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListAdapterUpdater.h" +#import "IGListAdapterUpdaterInternal.h" + +#import +#import +#import + +#import "UICollectionView+IGListBatchUpdateData.h" + +@implementation IGListAdapterUpdater { + BOOL _canBackgroundReload; +} + +- (instancetype)init { + IGAssertMainThread(); + + if (self = [super init]) { + // the default is to use animations unless NO is passed + _queuedUpdateIsAnimated = YES; + + _completionBlocks = [[NSMutableArray alloc] init]; + _itemUpdateBlocks = [[NSMutableArray alloc] init]; + + _reloadSections = [[NSMutableIndexSet alloc] init]; + + _deleteIndexPaths = [[NSMutableSet alloc] init]; + _insertIndexPaths = [[NSMutableSet alloc] init]; + _reloadIndexPaths = [[NSMutableSet alloc] init]; + + _canBackgroundReload = [[[UIDevice currentDevice] systemVersion] compare:@"8.3" options:NSNumericSearch] != NSOrderedAscending; + } + return self; +} + + +#pragma mark - Private API + +- (BOOL)hasChanges { + return self.hasQueuedReloadData + || self.itemUpdateBlocks.count > 0 + || self.fromObjects != nil + || self.toObjects != nil; +} + +- (void)performReloadDataWithCollectionView:(UICollectionView *)collectionView { + IGAssertMainThread(); + + // bail early if the collection view has been deallocated in the time since the update was queued + if (collectionView == nil) { + return; + } + + id delegate = self.delegate; + void (^reloadUpdates)() = self.reloadUpdates; + NSArray *completionBlocks = [self.completionBlocks copy]; + NSArray *itemUpdateBlocks = [self.itemUpdateBlocks copy]; + + // item updates must not send mutations to the collection view while we are reloading + self.batchUpdateOrReloadInProgress = YES; + + if (reloadUpdates) { + reloadUpdates(); + } + + // execute all stored item update blocks even if we are just calling reloadData. the actual collection view + // mutations will be discarded, but clients are encouraged to put their actually /data/ mutations inside the + // update block as well, so if we don't execute the block the changes will never happen + for (IGListItemUpdateBlock itemUpdateBlock in itemUpdateBlocks) { + itemUpdateBlock(); + } + + // cleanup state before reloading and calling completion blocks + [self cleanupState]; + [self cleanupUpdateBlockState]; + + [delegate listAdapterUpdater:self willReloadDataWithCollectionView:collectionView]; + [collectionView reloadData]; + [collectionView layoutIfNeeded]; + [delegate listAdapterUpdater:self didReloadDataWithCollectionView:collectionView]; + + self.batchUpdateOrReloadInProgress = NO; + + for (IGListUpdatingCompletion block in completionBlocks) { + block(YES); + } +} + +static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray> *objects) { + NSMutableSet *identifiers = [NSMutableSet new]; + NSMutableArray *uniqueObjects = [NSMutableArray new]; + for (id object in objects) { + id diffIdentifier = [object diffIdentifier]; + if (![identifiers containsObject:diffIdentifier]) { + [identifiers addObject:diffIdentifier]; + [uniqueObjects addObject:object]; + } else { + IGLKLog(@"WARNING: Object %@ already appeared in objects array", object); + } + } + return [uniqueObjects copy]; +} + +- (void)performBatchUpdatesWithCollectionView:(UICollectionView *)collectionView { + IGAssertMainThread(); + IGAssert(!self.batchUpdateOrReloadInProgress, @"should not call this when updating"); + + // bail early if the collection view has been deallocated in the time since the update was queued + if (collectionView == nil) { + return; + } + + // create local variables so we can immediately clean our state but pass these items into the batch update block + id delegate = self.delegate; + NSArray *fromObjects = [self.fromObjects copy]; + NSArray *toObjects = objectsWithDuplicateIdentifiersRemoved(self.toObjects); + void (^objectTransitionBlock)(NSArray *) = [self.objectTransitionBlock copy]; + NSArray *itemUpdateBlocks = [self.itemUpdateBlocks copy]; + NSArray *completionBlocks = [self.completionBlocks copy]; + const BOOL animated = self.queuedUpdateIsAnimated; + + // clean up all state so that new updates can be coalesced while the current update is in flight + [self cleanupState]; + + void (^executeUpdateBlocks)() = ^{ + // run the update block so that the adapter can set its items. this makes sure that just before the update is + // committed that the data source is updated to the /latest/ "toObjects". this makes the data source in sync + // with the items that the updater is transitioning to + if (objectTransitionBlock != nil) { + objectTransitionBlock(toObjects); + } + + // execute each item update block which should make calls like insert, delete, and reload for index paths + // we collect all mutations in corresponding sets on self, then filter based on UICollectionView shortcomings + // call after the objectTransitionBlock so section level mutations happen before any items + for (IGListItemUpdateBlock itemUpdateBlock in itemUpdateBlocks) { + itemUpdateBlock(); + } + }; + + void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) { + for (IGListUpdatingCompletion block in completionBlocks) { + block(finished); + } + }; + + // if the collection view isn't in a visible window, skip diffing and batch updating. execute all transition blocks, + // reload data, execute completion blocks, and get outta here + if (_canBackgroundReload && collectionView.window == nil) { + [self beginPerformBatchUpdatestoObjects:toObjects]; + executeUpdateBlocks(); + [self cleanupUpdateBlockState]; + [self performBatchUpdatesItemBlockApplied]; + [collectionView reloadData]; + [self endPerformBatchUpdates]; + executeCompletionBlocks(YES); + return; + } + + IGListIndexSetResult *result = IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, self.experiments); + + // if the diff has no changes and there are no update blocks queued, dont batch update + if (!result.hasChanges && itemUpdateBlocks.count == 0) { + executeUpdateBlocks(); + executeCompletionBlocks(YES); + return; + } + + __block IGListBatchUpdateData *updateData = nil; + + void (^updateBlock)() = ^{ + executeUpdateBlocks(); + + updateData = [self flushCollectionView:collectionView + withDiffResult:result + reloadSections:[self.reloadSections copy] + deleteIndexPaths:[self.deleteIndexPaths copy] + insertIndexPaths:[self.insertIndexPaths copy] + reloadIndexPaths:[self.reloadIndexPaths copy] + fromObjects:fromObjects]; + + [self cleanupUpdateBlockState]; + [self performBatchUpdatesItemBlockApplied]; + }; + + void (^completionBlock)(BOOL) = ^(BOOL finished) { + [self endPerformBatchUpdates]; + + executeCompletionBlocks(finished); + + [delegate listAdapterUpdater:self didPerformBatchUpdates:updateData withCollectionView:collectionView]; + + // queue another update in case something changed during batch updates. this method will bail next runloop if + // there are no changes + [self queueUpdateWithCollectionView:collectionView]; + }; + + // disables multiple performBatchUpdates: from happening at the same time + [self beginPerformBatchUpdatestoObjects:toObjects]; + + @try { + [delegate listAdapterUpdater:self willPerformBatchUpdatesWithCollectionView:collectionView]; + + if (IGListExperimentEnabled(self.experiments, IGListExperimentLayoutBeforeUpdate)) { + /** + There are traces where UICollectionView throws "Invalid update: invalid number of items in section i..." where + logs show that the problem section has a different "before" item count than the assert claims. Our hunch is + that there is some state corruption from too many unanimated updates happening rapidly and layout state + becoming out of sync. + */ + [collectionView layoutIfNeeded]; + } + + if (animated) { + [collectionView performBatchUpdates:updateBlock completion:completionBlock]; + } else { + [UIView performWithoutAnimation:^{ + [collectionView performBatchUpdates:updateBlock completion:completionBlock]; + }]; + } + } @catch (NSException *exception) { + [delegate listAdapterUpdater:self willCrashWithException:exception fromObjects:fromObjects toObjects:toObjects updates:updateData]; + @throw exception; + } +} + +- (NSSet *)filterIndexPaths:(NSSet *)indexPaths removingSections:(NSIndexSet *)sections { + NSMutableSet *filteredIndexPaths = [indexPaths mutableCopy]; + for (NSIndexPath *indexPath in indexPaths) { + const NSUInteger section = indexPath.section; + if ([sections containsIndex:section]) { + [filteredIndexPaths removeObject:indexPath]; + } + } + return filteredIndexPaths; +} + +void convertReloadToDeleteInsert(NSMutableIndexSet *reloads, + NSMutableIndexSet *deletes, + NSMutableIndexSet *inserts, + IGListIndexSetResult *result, + NSArray> *fromObjects) { + // reloadSections: is unsafe to use within performBatchUpdates:, so instead convert all reloads into deletes+inserts + const BOOL hasObjects = [fromObjects count] > 0; + [[reloads copy] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + // if a diff was not performed, there are no changes. instead use the same index that was originally queued + id diffIdentifier = hasObjects ? [fromObjects[idx] diffIdentifier] : nil; + const NSUInteger from = hasObjects ? [result oldIndexForIdentifier:diffIdentifier] : idx; + const NSUInteger to = hasObjects ? [result newIndexForIdentifier:diffIdentifier] : idx; + [reloads removeIndex:from]; + [deletes addIndex:from]; + [inserts addIndex:to]; + }]; +} + +- (IGListBatchUpdateData *)flushCollectionView:(UICollectionView *)collectionView + withDiffResult:(IGListIndexSetResult *)diffResult + reloadSections:(NSIndexSet *)reloadSections + deleteIndexPaths:(NSSet *)deleteIndexPaths + insertIndexPaths:(NSSet *)insertIndexPaths + reloadIndexPaths:(NSSet *)reloadIndexPaths + fromObjects:(NSArray > *)fromObjects { + NSSet *moves = [[NSSet alloc] initWithArray:diffResult.moves]; + + // combine section reloads from the diff and manual reloads via reloadItems: + NSMutableIndexSet *reloads = [diffResult.updates mutableCopy]; + [reloads addIndexes:reloadSections]; + + NSMutableIndexSet *inserts = [diffResult.inserts mutableCopy]; + NSMutableIndexSet *deletes = [diffResult.deletes mutableCopy]; + if (self.movesAsDeletesInserts) { + for (IGListMoveIndex *move in moves) { + [deletes addIndex:move.from]; + [inserts addIndex:move.to]; + } + // clear out all moves + moves = [NSSet new]; + } + + // reloadSections: is unsafe to use within performBatchUpdates:, so instead convert all reloads into deletes+inserts + convertReloadToDeleteInsert(reloads, deletes, inserts, diffResult, fromObjects); + + IGListBatchUpdateData *updateData = [[IGListBatchUpdateData alloc] initWithInsertSections:[inserts copy] + deleteSections:[deletes copy] + moveSections:moves + insertIndexPaths:insertIndexPaths + deleteIndexPaths:deleteIndexPaths + reloadIndexPaths:reloadIndexPaths]; + [collectionView ig_applyBatchUpdateData:updateData]; + return updateData; +} + +- (void)beginPerformBatchUpdatestoObjects:(NSArray *)toObjects { + self.batchUpdateOrReloadInProgress = YES; + self.pendingTransitionToObjects = toObjects; +} + +- (void)performBatchUpdatesItemBlockApplied { + self.pendingTransitionToObjects = nil; +} + +- (void)endPerformBatchUpdates { + self.batchUpdateOrReloadInProgress = NO; +} + +- (NSArray *)trimmedIndexPaths:(NSArray *)indexPaths inSections:(NSIndexSet *)sections { + NSMutableArray *paths = [indexPaths mutableCopy]; + for (NSInteger i = 0; i < paths.count; i++) { + if ([sections containsIndex:[paths[i] section]]) { + [paths removeObjectAtIndex:i]; + } + } + return paths; +} + +- (void)cleanupState { + self.queuedUpdateIsAnimated = YES; + + // destroy to/from transition items + self.fromObjects = nil; + self.toObjects = nil; + + // destroy reloadData state + self.reloadUpdates = nil; + self.queuedReloadData = NO; + + // remove indexpath/item changes + self.objectTransitionBlock = nil; + [self.itemUpdateBlocks removeAllObjects]; + + // remove completion blocks from item transitions or index path updates + [self.completionBlocks removeAllObjects]; +} + +- (void)cleanupUpdateBlockState { + [self.reloadSections removeAllIndexes]; + [self.deleteIndexPaths removeAllObjects]; + [self.insertIndexPaths removeAllObjects]; + [self.reloadIndexPaths removeAllObjects]; +} + +- (void)queueUpdateWithCollectionView:(UICollectionView *)collectionView { + IGAssertMainThread(); + + // callers may hold weak refs and lose the collection view by the time we requeue, bail if that's the case + if (collectionView == nil) { + return; + } + + __weak __typeof__(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + if (weakSelf.batchUpdateOrReloadInProgress || ![weakSelf hasChanges]) { + return; + } + + if (weakSelf.hasQueuedReloadData) { + [weakSelf performReloadDataWithCollectionView:collectionView]; + } else { + [weakSelf performBatchUpdatesWithCollectionView:collectionView]; + } + }); +} + + +#pragma mark - IGListUpdatingDelegate + +static BOOL IGListIsEqual(const void *a, const void *b, NSUInteger (*size)(const void *item)) { + const id left = (__bridge id)a; + const id right = (__bridge id)b; + return [[left diffIdentifier] isEqual:[right diffIdentifier]]; +} + +// since the diffing algo used in this updater keys items based on their -diffIdentifier, we must use a map table that +// precisely mimics this behavior +static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(const void *item)) { + return [[(__bridge id)item diffIdentifier] hash]; +} + +- (NSPointerFunctions *)objectLookupPointerFunctions { + NSPointerFunctions *functions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory]; + functions.hashFunction = IGListIdentifierHash; + functions.isEqualFunction = IGListIsEqual; + return functions; +} + +- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView + fromObjects:(nullable NSArray *)fromObjects + toObjects:(nullable NSArray *)toObjects + animated:(BOOL)animated + objectTransitionBlock:(void (^)(NSArray *))objectTransitionBlock + completion:(nullable void (^)(BOOL))completion { + IGAssertMainThread(); + IGParameterAssert(collectionView != nil); + IGParameterAssert(objectTransitionBlock != nil); + + // only update the items that we are coming from if it has not been set + // this allows multiple updates to be called while an update is already in progress, and the transition from > to + // will be done on the first "fromObjects" received and the last "toObjects" + // if performBatchUpdates: hasn't applied the update block, then data source hasn't transitioned its state. if an + // update is queued in between then we must use the pending toObjects + self.fromObjects = self.fromObjects ?: self.pendingTransitionToObjects ?: fromObjects; + self.toObjects = toObjects; + + // disabled animations will always take priority + // reset to YES in -cleanupState + self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated; + +#ifdef DEBUG + for (id obj in toObjects) { + IGAssert([obj conformsToProtocol:@protocol(IGListDiffable)], + @"In order to use IGListAdapterUpdater, object %@ must conform to IGListDiffable", obj); + } +#endif + + // always use the last update block, even though this should always do the exact same thing + self.objectTransitionBlock = objectTransitionBlock; + + IGListUpdatingCompletion localCompletion = completion; + if (localCompletion) { + [self.completionBlocks addObject:localCompletion]; + } + + [self queueUpdateWithCollectionView:collectionView]; +} + +- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView + animated:(BOOL)animated + itemUpdates:(void (^)())itemUpdates + completion:(void (^)(BOOL))completion { + IGAssertMainThread(); + IGParameterAssert(collectionView != nil); + IGParameterAssert(itemUpdates != nil); + + // disabled animations will always take priority + // reset to YES in -cleanupState + self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated; + + [self.itemUpdateBlocks addObject:itemUpdates]; + + if (completion != nil) { + [self.completionBlocks addObject:completion]; + } + + [self queueUpdateWithCollectionView:collectionView]; +} + +- (void)insertItemsIntoCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { + IGAssertMainThread(); + IGParameterAssert(collectionView != nil); + IGParameterAssert(indexPaths != nil); + if (self.batchUpdateOrReloadInProgress) { + [self.insertIndexPaths addObjectsFromArray:indexPaths]; + } else { + [self.delegate listAdapterUpdater:self willInsertIndexPaths:indexPaths collectionView:collectionView]; + [collectionView insertItemsAtIndexPaths:indexPaths]; + } +} + +- (void)deleteItemsFromCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { + IGAssertMainThread(); + IGParameterAssert(collectionView != nil); + IGParameterAssert(indexPaths != nil); + if (self.batchUpdateOrReloadInProgress) { + [self.deleteIndexPaths addObjectsFromArray:indexPaths]; + } else { + [self.delegate listAdapterUpdater:self willDeleteIndexPaths:indexPaths collectionView:collectionView]; + [collectionView deleteItemsAtIndexPaths:indexPaths]; + } +} + +- (void)reloadItemsInCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { + IGAssertMainThread(); + IGParameterAssert(collectionView != nil); + IGParameterAssert(indexPaths != nil); + if (self.batchUpdateOrReloadInProgress) { + [self.reloadIndexPaths addObjectsFromArray:indexPaths]; + } else { + [self.delegate listAdapterUpdater:self willReloadIndexPaths:indexPaths collectionView:collectionView]; + [collectionView reloadItemsAtIndexPaths:indexPaths]; + } +} + +- (void)reloadDataWithCollectionView:(UICollectionView *)collectionView + reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock + completion:(nullable IGListUpdatingCompletion)completion { + IGAssertMainThread(); + IGParameterAssert(collectionView != nil); + IGParameterAssert(reloadUpdateBlock != nil); + + IGListUpdatingCompletion localCompletion = completion; + if (localCompletion) { + [self.completionBlocks addObject:localCompletion]; + } + + self.reloadUpdates = reloadUpdateBlock; + self.queuedReloadData = YES; + [self queueUpdateWithCollectionView:collectionView]; +} + +- (void)reloadCollectionView:(UICollectionView *)collectionView sections:(NSIndexSet *)sections { + IGAssertMainThread(); + IGParameterAssert(collectionView != nil); + IGParameterAssert(sections != nil); + if (self.batchUpdateOrReloadInProgress) { + [self.reloadSections addIndexes:sections]; + } else { + [self.delegate listAdapterUpdater:self willReloadSections:sections collectionView:collectionView]; + [collectionView reloadSections:sections]; + } +} + +@end diff --git a/Source/IGListAdapterUpdaterDelegate.h b/Source/IGListAdapterUpdaterDelegate.h new file mode 100644 index 000000000..a50e2465d --- /dev/null +++ b/Source/IGListAdapterUpdaterDelegate.h @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@class IGListAdapterUpdater; + +NS_ASSUME_NONNULL_BEGIN + +/** + A protocol that receives events about IGListAdapterUpdater operations. + */ +@protocol IGListAdapterUpdaterDelegate + +/** + Notifies the delegate that the updater will call -[UICollectionView performBatchUpdates:completion:]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param collectionView The collection view that will perform the batch updates. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willPerformBatchUpdatesWithCollectionView:(UICollectionView *)collectionView; + +/** + Notifies the delegate that the updater succesfully finished -[UICollectionView performBatchUpdates:completion:]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param collectionView The collection view that performed the batch updates. + + @discussion This event is called in the completion block of the batch update. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater didPerformBatchUpdates:(IGListBatchUpdateData *)updates withCollectionView:(UICollectionView *)collectionView; + +/** + Notifies the delegate that the updater will call -[UICollectionView insertItemsAtIndexPaths:]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param indexPaths An array of index paths that will be inserted. + @param collectionView The collection view that will perform the insert. + + @discussion This event is only sent when outside of -[UICollectionView performBatchUpdates:completion:]. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willInsertIndexPaths:(NSArray *)indexPaths collectionView:(UICollectionView *)collectionView; + +/** + Notifies the delegate that the updater will call -[UICollectionView deleteItemsAtIndexPaths:]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param indexPaths An array of index paths that will be deleted. + @param collectionView The collection view that will perform the delete. + + @discussion This event is only sent when outside of -[UICollectionView performBatchUpdates:completion:]. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willDeleteIndexPaths:(NSArray *)indexPaths collectionView:(UICollectionView *)collectionView; + +/** + Notifies the delegate that the updater will call -[UICollectionView reloadItemsAtIndexPaths:]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param indexPaths An array of index paths that will be reloaded. + @param collectionView The collection view that will perform the reload. + + @discussion This event is only sent when outside of -[UICollectionView performBatchUpdates:completion:]. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willReloadIndexPaths:(NSArray *)indexPaths collectionView:(UICollectionView *)collectionView; + +/** + Notifies the delegate that the updater will call -[UICollectionView reloadSections:]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param sections The sections that will be reloaded + @param collectionView The collection view that will perform the reload. + + @discussion This event is only sent when outside of -[UICollectionView performBatchUpdates:completion:]. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willReloadSections:(NSIndexSet *)sections collectionView:(UICollectionView *)collectionView; + +/** + Notifies the delegate that the updater will call -[UICollectionView reloadData]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param collectionView The collection view that will be reloaded. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willReloadDataWithCollectionView:(UICollectionView *)collectionView; + +/** + Notifies the delegate that the updater successfully called -[UICollectionView reloadData]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param collectionView The collection view that reloaded. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater didReloadDataWithCollectionView:(UICollectionView *)collectionView; + +/** + Notifies the delegate that the collection view threw an exception in -[UICollectionView performBatchUpdates:completion:]. + + @param listAdapterUpdater The adapter updater owning the transition. + @param exception The exception thrown by the collection view. + @param fromObjects The items transitioned from in the diff, if any. + @param toObjects The items transitioned to in the diff, if any. + @param updates The batch updates that were applied to the collection view. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater + willCrashWithException:(NSException *)exception + fromObjects:(nullable NSArray *)fromObjects + toObjects:(nullable NSArray *)toObjects + updates:(IGListBatchUpdateData *)updates; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListAssert.h b/Source/IGListAssert.h new file mode 100644 index 000000000..361947398 --- /dev/null +++ b/Source/IGListAssert.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#ifndef IGAssert +#define IGAssert( condition, ... ) NSCAssert( (condition) , ##__VA_ARGS__) +#endif // #ifndef IGAssert + +#ifndef IGParameterAssert +#define IGParameterAssert( condition ) IGAssert( (condition) , @"Invalid parameter not satisfying: %@", @#condition) +#endif // #ifndef IGParameterAssert + +#ifndef IGAssertMainThread +#define IGAssertMainThread() IGAssert( ([NSThread isMainThread] == YES), @"Must be on the main thread") +#endif // #ifndef IGAssertMainThread diff --git a/Source/IGListBatchUpdateData.h b/Source/IGListBatchUpdateData.h new file mode 100644 index 000000000..88b2324dc --- /dev/null +++ b/Source/IGListBatchUpdateData.h @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + This object takes section indexes and item index paths and performs cleanup on init in order to perform a crash-free + update via -[UICollectionView performBatchUpdates:completion:]. + */ +IGLK_SUBCLASSING_RESTRICTED +@interface IGListBatchUpdateData : NSObject + +/** + Clean section insert indexes. + */ +@property (nonatomic, strong, readonly) NSIndexSet *insertSections; + +/** + Clean section delete indexes. + */ +@property (nonatomic, strong, readonly) NSIndexSet *deleteSections; + +/** + Clean section moves. + */ +@property (nonatomic, strong, readonly) NSSet *moveSections; + +/** + Clean item insert index paths. + */ +@property (nonatomic, strong, readonly) NSSet *insertIndexPaths; + +/** + Clean item delete index paths. + */ +@property (nonatomic, strong, readonly) NSSet *deleteIndexPaths; + +/** + Clean item reload index paths. + */ +@property (nonatomic, strong, readonly) NSSet *reloadIndexPaths; + +/** + Create a new batch update object with section and item operations. + + @param insertSections Section indexes to insert. + @param deleteSections Section indexes to delete. + @param moveSections Section moves. + @param insertIndexPaths Item index paths to insert. + @param deleteIndexPaths Item index paths to delete. + @param reloadIndexPaths Item index paths to reload. + + @return A new batch update object with cleaned update operations. + */ +- (instancetype)initWithInsertSections:(NSIndexSet *)insertSections + deleteSections:(NSIndexSet *)deleteSections + moveSections:(NSSet *)moveSections + insertIndexPaths:(NSSet *)insertIndexPaths + deleteIndexPaths:(NSSet *)deleteIndexPaths + reloadIndexPaths:(NSSet *)reloadIndexPaths NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListBatchUpdateData.mm b/Source/IGListBatchUpdateData.mm new file mode 100644 index 000000000..f2c5fe32e --- /dev/null +++ b/Source/IGListBatchUpdateData.mm @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListBatchUpdateData.h" + +#import + +#import + +// Filters indexPaths removing all paths that have a section in sections. +static NSMutableSet *indexPathsMinusSections(NSSet *indexPaths, NSIndexSet *sections) { + NSMutableSet *filteredIndexPaths = [indexPaths mutableCopy]; + for (NSIndexPath *indexPath in indexPaths) { + const NSUInteger section = indexPath.section; + if ([sections containsIndex:section]) { + [filteredIndexPaths removeObject:indexPath]; + } + } + return filteredIndexPaths; +} + +// Plucks the given move from available moves and turns it into a delete + insert +static void convertMoveToDeleteAndInsert(NSMutableSet *moves, + IGListMoveIndex *move, + NSMutableIndexSet *deletes, + NSMutableIndexSet *inserts) { + [moves removeObject:move]; + + // add a delete and insert respecting the move's from and to sections + // delete + insert will result in reloading the entire section + [deletes addIndex:move.from]; + [inserts addIndex:move.to]; +} + +@implementation IGListBatchUpdateData + +// Converts all moves that have section operations into a section delete + insert. ++ (void)cleanIndexSetWithMap:(const std::unordered_map &)map + moves:(NSMutableSet *)moves + sections:(NSMutableIndexSet *)sections + deletes:(NSMutableIndexSet *)deletes + inserts:(NSMutableIndexSet *)inserts { + for(const auto &i : map) { + const NSUInteger index = i.first; + if ([sections containsIndex:index]) { + [sections removeIndex:index]; + convertMoveToDeleteAndInsert(moves, i.second, deletes, inserts); + } + } +} + +// Converts all section moves that have index path operations into a section delete + insert. ++ (void)cleanIndexPathsWithMap:(const std::unordered_map &)map + moves:(NSMutableSet *)moves + indexPaths:(NSMutableSet *)indexPaths + deletes:(NSMutableIndexSet *)deletes + inserts:(NSMutableIndexSet *)inserts { + for (NSIndexPath *path in [indexPaths copy]) { + const auto it = map.find(path.section); + if (it != map.end() && it->second != nil) { + [indexPaths removeObject:path]; + convertMoveToDeleteAndInsert(moves, it->second, deletes, inserts); + } + } +} + +/** + Converts all section moves that are also reloaded, or have index path inserts, deletes, or reloads into a section + delete + insert in order to avoid UICollectionView heap corruptions, exceptions, and animation/snapshot bugs. + */ +- (instancetype)initWithInsertSections:(NSIndexSet *)insertSections + deleteSections:(NSIndexSet *)deleteSections + moveSections:(NSSet *)moveSections + insertIndexPaths:(NSSet *)insertIndexPaths + deleteIndexPaths:(NSSet *)deleteIndexPaths + reloadIndexPaths:(NSSet *)reloadIndexPaths { + IGParameterAssert(insertSections != nil); + IGParameterAssert(deleteSections != nil); + IGParameterAssert(moveSections != nil); + IGParameterAssert(insertIndexPaths != nil); + IGParameterAssert(deleteIndexPaths != nil); + IGParameterAssert(reloadIndexPaths != nil); + if (self = [super init]) { + NSMutableSet *mMoveSections = [moveSections mutableCopy]; + NSMutableIndexSet *mDeleteSections = [deleteSections mutableCopy]; + NSMutableIndexSet *mInsertSections = [insertSections mutableCopy]; + + // these collections should NEVER be mutated during cleanup passes, otherwise sections that have multiple item + // changes (e.g. a moved section that has a delete + reload on different index paths w/in the section) will only + // convert one of the item changes into a section delete+insert. this will fail hard and be VERY difficult to + // debug + const NSUInteger moveCount = [moveSections count]; + std::unordered_map fromMap(moveCount); + std::unordered_map toMap(moveCount); + for (IGListMoveIndex *move in moveSections) { + const NSUInteger from = move.from; + const NSUInteger to = move.to; + + // if the move is already deleted or inserted, discard it and use delete+insert instead + if ([deleteSections containsIndex:from] || [insertSections containsIndex:to]) { + convertMoveToDeleteAndInsert(mMoveSections, move, mDeleteSections, mInsertSections); + } else { + fromMap[from] = move; + toMap[to] = move; + } + } + + NSMutableSet *mInsertIndexPaths = [insertIndexPaths mutableCopy]; + NSMutableSet *mDeleteIndexPaths = [deleteIndexPaths mutableCopy]; + + // UICollectionView will throw if reloading an index path in a section that is also deleted + NSMutableSet *mReloadIndexPaths = indexPathsMinusSections(reloadIndexPaths, deleteSections); + + // UICollectionView will throw about simultaneous animations when reloading and moving cells at the same time + [IGListBatchUpdateData cleanIndexPathsWithMap:fromMap moves:mMoveSections indexPaths:mReloadIndexPaths deletes:mDeleteSections inserts:mInsertSections]; + + // avoids a bug where a cell is animated twice and one of the snapshot cells is never removed from the hierarchy + [IGListBatchUpdateData cleanIndexPathsWithMap:fromMap moves:mMoveSections indexPaths:mDeleteIndexPaths deletes:mDeleteSections inserts:mInsertSections]; + + // prevents a bug where UICollectionView corrupts the heap memory when inserting into a section that is moved + [IGListBatchUpdateData cleanIndexPathsWithMap:toMap moves:mMoveSections indexPaths:mInsertIndexPaths deletes:mDeleteSections inserts:mInsertSections]; + + _deleteSections = [mDeleteSections copy]; + _insertSections = [mInsertSections copy]; + _moveSections = [mMoveSections copy]; + _deleteIndexPaths = [mDeleteIndexPaths copy]; + _insertIndexPaths = [mInsertIndexPaths copy]; + _reloadIndexPaths = [mReloadIndexPaths copy]; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@ %p; deleteSections: %zi; insertSections: %zi; moveSections: %zi; deleteIndexPaths: %zi; insertIndexPaths: %zi; reloadIndexPaths: %zi;>", + NSStringFromClass(self.class), self, self.deleteSections.count, self.insertSections.count, self.moveSections.count, + self.deleteIndexPaths.count, self.insertIndexPaths.count, self.reloadIndexPaths.count]; +} + +@end diff --git a/Source/IGListCollectionContext.h b/Source/IGListCollectionContext.h new file mode 100644 index 000000000..5869b39e8 --- /dev/null +++ b/Source/IGListCollectionContext.h @@ -0,0 +1,177 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class IGListSectionController; +@protocol IGListSectionType; + +/** + The collection context provides limited access to the collection related information that + section controllers need for things like sizing, dequeing cells, insterting/deleting/reloading, etc. + */ +@protocol IGListCollectionContext + +/** + Size of the collection view. Provided primarily for sizing cells. + */ +@property (nonatomic, readonly) CGSize containerSize; + +/** + Query for the index a cell in the collection relative to the section controller. + + @param cell An existing cell in the collection. + @param sectionController The section controller requesting this information. + + @return The index of the cell or NSNotFound if it does not exist in the collection. + */ +- (NSUInteger)indexForCell:(UICollectionViewCell *)cell + sectionController:(IGListSectionController *)sectionController; + +/** + Query for a cell in the collection. May return nil if the cell is offscreen. + + @param index The index of the desired cell. + @param sectionController The section controller requesting this information. + + @return The collection view cell, or `nil` if not found. + */ +- (nullable __kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index + sectionController:(IGListSectionController *)sectionController; + +/** + Query for the visible cells for the given section controller. + + @param sectionController The section controller requesting this information. + + @return An array of visible cells, or an empty array if none are found. + */ +- (NSArray *)visibleCellsForSectionController:(IGListSectionController *)sectionController; + +/** + Deselects a cell in the collection. + + @param index The index of the item to deselect. + @param sectionController The section controller requesting this information. + @param animated Pass `YES` to animate the change, `NO` otherwise. + */ +- (void)deselectItemAtIndex:(NSInteger)index + sectionController:(IGListSectionController *)sectionController + animated:(BOOL)animated; + +/** + Query the section index of an section controller. + + @param sectionController An section controller object. + + @return The section index of the list if found, otherwise `NSNotFound`. + */ +- (NSUInteger)sectionForSectionController:(IGListSectionController *)sectionController; + +/** + Dequeues a cell from the UICollectionView reuse pool. + + @param cellClass The class of the cell you want to dequeue. + @param sectionController The section controller requesting this information. + @param index The index of the cell. + + @return A cell dequeued from the reuse pool or newly created. + + @note This method uses a string representation of the cell class as the identifier. + */ +- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index; + +/** + Dequeues a supplementary view from the UICollectionView reuse pool. + + @param elementKind The kind of supplementary veiw. + @param sectionController The section controller requesting this information. + @param viewClass The class of the supplementary view. + @param index The index of the supplementary vew. + + @return A supplementary view dequeued from the reuse pool or newly created. + + @note This method uses a string representation of the view class as the identifier. + */ +- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind + forSectionController:(IGListSectionController *)sectionController + class:(Class)viewClass + atIndex:(NSInteger)index; + +/** + Reloads cells in the section controller. + + @param sectionController The section controller who's cells need reloading. + @param indexes The indexes of items that need reloading. + */ +- (void)reloadInSectionController:(IGListSectionController *)sectionController + atIndexes:(NSIndexSet *)indexes; + +/** + Inserts cells in the feed. + + @param sectionController The section controller who's cells need inserting. + @param indexes The indexes of items that need inserting. + */ +- (void)insertInSectionController:(IGListSectionController *)sectionController + atIndexes:(NSIndexSet *)indexes; + +/** + Deletes cells in the feed. + + @param sectionController The section controller who's cells need deleted. + @param indexes The indexes of items that need deleting. + */ +- (void)deleteInSectionController:(IGListSectionController *)sectionController + atIndexes:(NSIndexSet *)indexes; + +/** + Reload the entire section controller. + + @param sectionController The list object who's cells need reloading. + */ +- (void)reloadSectionController:(IGListSectionController *)sectionController; + +/** + Batch many cell-level updates in a single transaction. + + @param animated A flag indicating if the transition should be animated. + @param updates A block containing all of the cell updates. + @param completion An optional completion block to execute when the updates are finished. + + @discussion Use this method to batch cell updates (inserts, deletes, reloads) into a single transaction. This lets you + make many changes to your data store and perform all the transitions at once. + + For example, inside your section controllers, you may want to delete /and/ insert into the data source that backs your + section controller: + + [self.collectionContext performBatchItemUpdates:^{ + // perform data source changes inside the update block + [self.items addObject:newItem]; + [self.items removeObjectAtIndex:0]; + + NSIndexSet *inserts = [NSIndexSet indexSetWithIndex:[self.items count] - 1]; + [self.collectionContext insertInSectionController:self atIndexes:inserts]; + + NSIndexSet *deletes = [NSIndexSet indexSetWithIndex:0]; + [self.collectionContext deleteInSectionController:self deletes]; + } completion:nil]; + + Note that you **must** perform data modifications **inside** the update block. Updates will not be performed + synchronously, so you should make sure that your data source changes only when necessary. + */ +- (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(nullable void (^)(BOOL finished))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListCollectionView.h b/Source/IGListCollectionView.h new file mode 100644 index 000000000..af612289f --- /dev/null +++ b/Source/IGListCollectionView.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +/** + This class is never actually used by the IGListKit infrastructure. It exists only to give compiler errors when editing + methods are called on the collection view returned by -[IGListAdapter collectionView]. + */ +IGLK_SUBCLASSING_RESTRICTED +@interface IGListCollectionView : UICollectionView + +- (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL))completion IGLK_UNAVAILABLE("Call -[IGListAdapter performUpdatesWithCompletion:] instead"); + +- (void)reloadData IGLK_UNAVAILABLE("Call -[IGListAdapter reloadDataWithCompletion:] instead"); +- (void)reloadSections:(NSIndexSet *)sections IGLK_UNAVAILABLE("Call -[IGListAdapter reloadItems:] instead"); + +- (void)insertSections:(NSIndexSet *)sections IGLK_UNAVAILABLE("Call -[IGListAdapter performUpdatesWithCompletion:] instead"); +- (void)deleteSections:(NSIndexSet *)sections IGLK_UNAVAILABLE("Call -[IGListAdapter performUpdatesWithCompletion:] instead"); +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection IGLK_UNAVAILABLE("Call -[IGListAdapter performUpdatesWithCompletion:] instead"); + +- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths IGLK_UNAVAILABLE("Call -[ insertSectionController:forItems:completion:] instead"); +- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths IGLK_UNAVAILABLE("Call -[ reloadSectionController:forItems:completion:] instead"); +- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths IGLK_UNAVAILABLE("Call -[ deleteSectionController:forItems:completion:] instead"); +- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath IGLK_UNAVAILABLE("Moving items currently unsupported"); + +- (void)setDelegate:(id)delegate IGLK_UNAVAILABLE("IGListAdapter should be the delegate of the collection view"); +- (void)setDataSource:(id)dataSource IGLK_UNAVAILABLE("IGListAdapter should be the data source of the collection view"); +- (void)setBackgroundView:(UIView *)backgroundView IGLK_UNAVAILABLE("Return a view in -[IGListAdapterDataSource emptyViewForListAdapter:] instead"); + +@end diff --git a/Source/IGListCollectionView.m b/Source/IGListCollectionView.m new file mode 100644 index 000000000..2361beb20 --- /dev/null +++ b/Source/IGListCollectionView.m @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListCollectionView.h" + +@interface IGListCollectionView () + +@property (nonatomic, assign, readonly) BOOL requiresManualWillDisplay; +@property (nonatomic, strong) NSSet *ig_visibleIndexPaths; + +@end + +@implementation IGListCollectionView + +- (void)commonInit { + self.backgroundColor = [UIColor whiteColor]; + self.alwaysBounceVertical = YES; + + // iOS 6 and 7 do not support -collectionView:willDisplayCell:forItemAtIndexPath: so we do it ourselves + _requiresManualWillDisplay = [[[UIDevice currentDevice] systemVersion] floatValue] < 8.0; +} + +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { + if (self = [super initWithFrame:frame collectionViewLayout:layout]) { + [self commonInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + [self commonInit]; + } + return self; +} + +- (void)layoutSubviews { + /** + UICollectionView will sometimes lay its cells out with an animation. This is especially noticeable on older devices + while scrolling quickly. The simplest fix is to just disable animations for -layoutSubviews, which is where cells + and other views inside the UICollectionView are laid out. + */ + [UIView performWithoutAnimation:^{ + [super layoutSubviews]; + }]; + + if (self.requiresManualWillDisplay && [self.delegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]) { + NSArray *indexPaths = [self indexPathsForVisibleItems]; + for (NSIndexPath *path in indexPaths) { + if (![self.ig_visibleIndexPaths containsObject:path]) { + UICollectionViewCell *cell = [self cellForItemAtIndexPath:path]; + [self.delegate collectionView:self willDisplayCell:cell forItemAtIndexPath:path]; + } + } + self.ig_visibleIndexPaths = [NSSet setWithArray:indexPaths]; + } +} + +@end diff --git a/Source/IGListDiff.h b/Source/IGListDiff.h new file mode 100644 index 000000000..586c842c4 --- /dev/null +++ b/Source/IGListDiff.h @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + An option on how to do comparisons between similar objects. + */ +typedef NS_ENUM(NSUInteger, IGListDiffOption) { + /** + Compare objects using pointer personality. + */ + IGListDiffPointerPersonality, + /** + Compare objects using -[NSObject isEqual:]. + */ + IGListDiffEquality +}; + +/** + Create a diff using indexes between two collections. + + @param oldArray The old objects to diff against. + @param newArray The new objects to diff with. + @param option An option on how to compare objects. + + @return Result object with effected indexes. + */ +FOUNDATION_EXTERN IGListIndexSetResult *IGListDiff(NSArray > *_Nullable oldArray, + NSArray> *_Nullable newArray, + IGListDiffOption option); + +/** + Create a diff using index paths between two collections. + + @param fromSection The old section used to seed returned index paths. + @param toSection The new section used to seed returned index paths. + @param oldArray The old objects to diff against. + @param newArray The new objects to diff with. + @param option An option on how to compare objects. + + @return Result object with effected index paths. + */ +FOUNDATION_EXTERN IGListIndexPathResult *IGListDiffPaths(NSInteger fromSection, + NSInteger toSection, + NSArray> *_Nullable oldArray, + NSArray> *_Nullable newArray, + IGListDiffOption option); + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListDiff.mm b/Source/IGListDiff.mm new file mode 100644 index 000000000..db6caa39e --- /dev/null +++ b/Source/IGListDiff.mm @@ -0,0 +1,301 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListDiff.h" + +#import +#import +#import + +#import + +#import +#import + +#import "IGListIndexPathResultInternal.h" +#import "IGListIndexSetResultInternal.h" +#import "IGListMoveIndexInternal.h" +#import "IGListMoveIndexPathInternal.h" + +using namespace std; + +/// Used to track data stats while diffing. +struct IGListEntry { + /// The number of times the data occurs in the old array + NSInteger oldCounter = 0; + /// The number of times the data occurs in the new array + NSInteger newCounter = 0; + /// The indexes of the data in the old array + stack oldIndexes; + /// Flag marking if the data has been updated between arrays by checking the isEqual: method + BOOL updated = NO; +}; + +/// Track both the entry and algorithm index. Default the index to NSNotFound +struct IGListRecord { + IGListEntry *entry; + mutable NSInteger index; + + IGListRecord() { + entry = NULL; + index = NSNotFound; + } +}; + +static id IGListTableKey(id object) { + id key = [object diffIdentifier]; + NSCAssert(key != nil, @"Cannot use a nil key for the diffIdentifier of object %@", object); + return key; +} + +struct IGListEqualID { + bool operator()(const id a, const id b) const { + return (a == b) || [a isEqual: b]; + } +}; + +struct IGListHashID { + size_t operator()(const id o) const { + return (size_t)[o hash]; + } +}; + +static id IGListDiffing(BOOL returnIndexPaths, + NSInteger fromSection, + NSInteger toSection, + NSArray> *oldArray, + NSArray> *newArray, + IGListDiffOption option, + IGListExperiment experiments) { + const NSInteger newCount = newArray.count; + const NSInteger oldCount = oldArray.count; + + // symbol table uses the old/new array diffIdentifier as the key and IGListEntry as the value + // using id as the key provided by https://lists.gnu.org/archive/html/discuss-gnustep/2011-07/msg00019.html + unordered_map, IGListEntry, IGListHashID, IGListEqualID> table; + + // pass 1 + // create an entry for every item in the new array + // increment its new count for each occurence + vector newResultsArray(newCount); + for (NSInteger i = 0; i < newCount; i++) { + id key = IGListTableKey(newArray[i]); + IGListEntry &entry = table[key]; + entry.newCounter++; + + // add NSNotFound for each occurence of the item in the new array + entry.oldIndexes.push(NSNotFound); + + // note: the entry is just a pointer to the entry which is stack-allocated in the table + newResultsArray[i].entry = &entry; + } + + // pass 2 + // update or create an entry for every item in the old array + // increment its old count for each occurence + // record the original index of the item in the old array + // MUST be done in descending order to respect the oldIndexes stack construction + vector oldResultsArray(oldCount); + for (NSInteger i = oldCount - 1; i >= 0; i--) { + id key = IGListTableKey(oldArray[i]); + IGListEntry &entry = table[key]; + entry.oldCounter++; + + // push the original indices where the item occured onto the index stack + entry.oldIndexes.push(i); + + // note: the entry is just a pointer to the entry which is stack-allocated in the table + oldResultsArray[i].entry = &entry; + } + + // pass 3 + // handle data that occurs in both arrays + for (NSInteger i = 0; i < newCount; i++) { + IGListEntry *entry = newResultsArray[i].entry; + + // grab and pop the top original index. if the item was inserted this will be NSNotFound + NSCAssert(!entry->oldIndexes.empty(), @"Old indexes is empty while iterating new item %zi. Should have NSNotFound", i); + const NSInteger originalIndex = entry->oldIndexes.top(); + entry->oldIndexes.pop(); + + if (originalIndex < oldCount) { + const id n = newArray[i]; + const id o = oldArray[originalIndex]; + switch (option) { + case IGListDiffPointerPersonality: + // flag the entry as updated if the pointers are not the same + if (n != o) { + entry->updated = YES; + } + break; + case IGListDiffEquality: + // use -[IGListDiffable isEqual:] between both version of data to see if anything has changed + // skip the equality check if both indexes point to the same object + if (n != o && ![n isEqual:o]) { + entry->updated = YES; + } + break; + } + } + if (originalIndex != NSNotFound + && entry->newCounter > 0 + && entry->oldCounter > 0) { + // if an item occurs in the new and old array, it is unique + // assign the index of new and old records to the opposite index (reverse lookup) + newResultsArray[i].index = originalIndex; + oldResultsArray[originalIndex].index = i; + } + } + + // storage for final NSIndexPaths or indexes + id mInserts, mMoves, mUpdates, mDeletes; + if (returnIndexPaths) { + mInserts = [NSMutableArray new]; + mMoves = [NSMutableArray new]; + mUpdates = [NSMutableArray new]; + mDeletes = [NSMutableArray new]; + } else { + mInserts = [NSMutableIndexSet new]; + mUpdates = [NSMutableIndexSet new]; + mDeletes = [NSMutableIndexSet new]; + mMoves = [NSMutableArray new]; + } + + // populate a container based on whether we want NSIndexPaths or indexes + // section into INDEX SET + // item, section into ARRAY + // IGListMoveIndex or IGListMoveIndexPath into ARRAY + void (^addIndexToCollection)(id, NSInteger, NSInteger, id) = ^(id collection, NSInteger section, NSInteger index, id obj) { + if (obj) { + [collection addObject:obj]; + } else if (returnIndexPaths) { + NSIndexPath *path = [NSIndexPath indexPathForItem:index inSection:section]; + [collection addObject:path]; + } else { + [collection addIndex:index]; + } + }; + + NSMapTable *oldMap = [NSMapTable strongToStrongObjectsMapTable]; + NSMapTable *newMap = [NSMapTable strongToStrongObjectsMapTable]; + void (^addIndexToMap)(NSInteger, NSInteger, NSArray *, NSMapTable *) = ^(NSInteger section, NSInteger index, NSArray *array, NSMapTable *map) { + id value; + if (returnIndexPaths) { + value = [NSIndexPath indexPathForItem:index inSection:section]; + } else { + value = @(index); + } + [map setObject:value forKey:[array[index] diffIdentifier]]; + }; + + // track offsets from deleted items to calculate where items have moved + vector deleteOffsets(oldCount), insertOffsets(newCount); + NSInteger runningOffset = 0; + + // iterate old array records checking for deletes + // incremement offset for each delete + for (NSInteger i = 0; i < oldCount; i++) { + deleteOffsets[i] = runningOffset; + const IGListRecord record = oldResultsArray[i]; + // if the record index in the new array doesn't exist, its a delete + if (record.index == NSNotFound) { + addIndexToCollection(mDeletes, fromSection, i, nil); + runningOffset++; + } + + addIndexToMap(fromSection, i, oldArray, oldMap); + } + + // reset and track offsets from inserted items to calculate where items have moved + runningOffset = 0; + + for (NSInteger i = 0; i < newCount; i++) { + insertOffsets[i] = runningOffset; + const IGListRecord record = newResultsArray[i]; + const NSInteger oldIndex = record.index; + // add to inserts if the opposing index is NSNotFound + if (record.index == NSNotFound) { + addIndexToCollection(mInserts, toSection, i, nil); + runningOffset++; + } else { + // note that an entry can be updated /and/ moved + if (record.entry->updated) { + addIndexToCollection(mUpdates, toSection, oldIndex, nil); + } + + // calculate the offset and determine if there was a move + // if the indexes match, ignore the index + const NSInteger insertOffset = insertOffsets[i]; + const NSInteger deleteOffset = deleteOffsets[oldIndex]; + if ((oldIndex - deleteOffset + insertOffset) != i) { + id move; + if (returnIndexPaths) { + NSIndexPath *from = [NSIndexPath indexPathForItem:oldIndex inSection:fromSection]; + NSIndexPath *to = [NSIndexPath indexPathForItem:i inSection:toSection]; + move = [[IGListMoveIndexPath alloc] initWithFrom:from to:to]; + } else { + move = [[IGListMoveIndex alloc] initWithFrom:oldIndex to:i]; + } + addIndexToCollection(mMoves, NSNotFound, NSNotFound, move); + } + } + + addIndexToMap(toSection, i, newArray, newMap); + } + + NSCAssert((oldCount + [mInserts count] - [mDeletes count]) == newCount, + @"Sanity check failed applying %zi inserts and %zi deletes to old count %zi equaling new count %zi", + oldCount, [mInserts count], [mDeletes count], newCount); + + if (returnIndexPaths) { + return [[IGListIndexPathResult alloc] initWithInserts:mInserts + deletes:mDeletes + updates:mUpdates + moves:mMoves + oldIndexPathMap:oldMap + newIndexPathMap:newMap]; + } else { + return [[IGListIndexSetResult alloc] initWithInserts:mInserts + deletes:mDeletes + updates:mUpdates + moves:mMoves + oldIndexMap:oldMap + newIndexMap:newMap]; + } +} + +IGListIndexSetResult *IGListDiff(NSArray > *oldArray, + NSArray> *newArray, + IGListDiffOption option) { + return IGListDiffing(NO, 0, 0, oldArray, newArray, option, 0); +} + +IGListIndexPathResult *IGListDiffPaths(NSInteger fromSection, + NSInteger toSection, + NSArray> *oldArray, + NSArray> *newArray, + IGListDiffOption option) { + return IGListDiffing(YES, fromSection, toSection, oldArray, newArray, option, 0); +} + +IGListIndexSetResult *IGListDiffExperiment(NSArray> *_Nullable oldArray, + NSArray> *_Nullable newArray, + IGListDiffOption option, + IGListExperiment experiments) { + return IGListDiffing(NO, 0, 0, oldArray, newArray, option, experiments); +} + +IGListIndexPathResult *IGListDiffPathsExperiment(NSInteger fromSection, + NSInteger toSection, + NSArray> *_Nullable oldArray, + NSArray> *_Nullable newArray, + IGListDiffOption option, + IGListExperiment experiments) { + return IGListDiffing(YES, fromSection, toSection, oldArray, newArray, option, experiments); +} diff --git a/Source/IGListDiffable.h b/Source/IGListDiffable.h new file mode 100644 index 000000000..d1db355c8 --- /dev/null +++ b/Source/IGListDiffable.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +/** + The IGListDiffable protocol provides the base methods needed to compare the identity and equality of two objects using + one of the IGKAlgorithm functions. + */ +@protocol IGListDiffable + +/** + Returns a key that uniquely identifies the object. + + @return A key that can be used to uniquely identify the object. + + @discussion Two objects may share the same identifier, but are not equal. A common pattern is to import IGListCommon.h + and use the NSObject category for automatic conformance. However this means that objects will be identified on their + pointer value so finding updates becomes impossible. + + @warning This value should never be mutated. + */ +- (nonnull id)diffIdentifier; + +/** + Returns a Boolean value that indicates whether the receiver and a given object are equal. + + @param object The object to be compared to the receiver. + + @return YES if the receiver and object are equal, otherwise NO. + + @warning If you implement a custom isEqual: you must also implement -hash. You can just use the -diffIdentifier value + for your hash function: + + - (NSUInteger)hash { + return [[self diffIdentifier] hash]; + } + */ +- (BOOL)isEqual:(nullable id)object; + +@end diff --git a/Source/IGListDisplayDelegate.h b/Source/IGListDisplayDelegate.h new file mode 100644 index 000000000..bc2291762 --- /dev/null +++ b/Source/IGListDisplayDelegate.h @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class IGListAdapter; +@class IGListSectionController; + +@protocol IGListSectionType; + +NS_ASSUME_NONNULL_BEGIN + +/** + Implement this protocol to receive display events for an section controller when it is on screen. + */ +@protocol IGListDisplayDelegate + +/** + Tells the delegate that the specified list is about to be displayed. + + @param listAdapter The list adapter that the list will display in. + @param sectionController The list about to be displayed. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController; + +/** + Tells the delegate that the specified list is no longer being displayed. + + @param listAdapter The list adapter that the list was displayed in. + @param sectionController The list that is no longer displayed. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController; + +/** + Tells the delegate that a row in the specified list is about to be displayed. + + @param listAdapter The list adapter that row will display in. + @param sectionController The section controller that is displaying. + @param cell The cell about to be displayed. + @param index The index of the row. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController + cell:(UICollectionViewCell *)cell + atIndex:(NSInteger)index; + +/** + Tells the delegate that a row in the specified list is no longer being displayed. + + @param listAdapter The list adapter that the list was displayed in. + @param sectionController The section controller that is no longer displaying the cell. + @param cell The cell that is no longer displayed. + @param index The index of the row. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController + cell:(UICollectionViewCell *)cell + atIndex:(NSInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListExperiments.h b/Source/IGListExperiments.h new file mode 100644 index 000000000..fd85f2ec5 --- /dev/null +++ b/Source/IGListExperiments.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +/** + Bitmask-able options used for prerelease feature testing. + */ +typedef NS_OPTIONS (NSUInteger, IGListExperiment) { + IGListExperimentLayoutBeforeUpdate = 1 << 0, + IGListExperimentUICVReloadedInSetter = 1 << 1, +}; + +/** + Check if an experiment is enabled in a bitmask. + + @param mask The bitmask of experiments. + @param option The option to compare with. + + @return YES if the option is in the bitmask, otherwise NO. + */ +static inline BOOL IGListExperimentEnabled(IGListExperiment mask, IGListExperiment option) { + return (mask & option) != 0; +} + +NS_ASSUME_NONNULL_BEGIN + +/** + Perform a diff with an experiment bitmask. See IGListDiff() in IGListDiff.h + */ +FOUNDATION_EXTERN IGListIndexSetResult *IGListDiffExperiment(NSArray> *_Nullable oldArray, + NSArray> *_Nullable newArray, + IGListDiffOption option, + IGListExperiment experiments); + +/** + Perform a diff with an experiment bitmask. See IGListDiffPaths() in IGListDiff.h + */ +FOUNDATION_EXTERN IGListIndexPathResult *IGListDiffPathsExperiment(NSInteger fromSection, + NSInteger toSection, + NSArray> *_Nullable oldArray, + NSArray> *_Nullable newArray, + IGListDiffOption option, + IGListExperiment experiments); + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListIndexPathResult.h b/Source/IGListIndexPathResult.h new file mode 100644 index 000000000..1076cb68c --- /dev/null +++ b/Source/IGListIndexPathResult.h @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Result object returned when diffing with sections. + */ +@interface IGListIndexPathResult : NSObject + +/** + Index paths inserted into the new collection. + */ +@property (nonatomic, copy, readonly) NSArray *inserts; + +/** + Index paths deleted from the old collection. + */ +@property (nonatomic, copy, readonly) NSArray *deletes; + +/** + Index paths in the new collection that need updated. + */ +@property (nonatomic, copy, readonly) NSArray *updates; + +/** + Moves from an index path in the old collection to an index path in the new collection. + */ +@property (nonatomic, copy, readonly) NSArray *moves; + +/** + Convenience to query if the result has any changes. + + @return YES if the result has changes, NO otherwise. + */ +- (BOOL)hasChanges; + +/** + Fetch the index path of the object with identifier before the diff. + + @param identifier The diff identifier of the object. See -[IGListDiffable diffIdentifier]. + + @return The index path of the object before the diff, or nil. + */ +- (nullable NSIndexPath *)oldIndexPathForIdentifier:(id)identifier; + +/** + Fetch the index path of the object with identifier after the diff. + + @param identifier The diff identifier of the object. See -[IGListDiffable diffIdentifier]. + + @return The index path of the object after the diff, or nil. + */ +- (nullable NSIndexPath *)newIndexPathForIdentifier:(id)identifier; + +/** + Create a new result object transforming index paths that are both moved and updated into delete and inserts. + + @discussion This is a convenience method for using a result object to perform UICollectionView and UITableView updates. + */ +- (IGListIndexPathResult *)resultWithUpdatedMovesAsDeleteInserts; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListIndexPathResult.m b/Source/IGListIndexPathResult.m new file mode 100644 index 000000000..15a0bd12f --- /dev/null +++ b/Source/IGListIndexPathResult.m @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListIndexPathResult.h" +#import "IGListIndexPathResultInternal.h" + +@implementation IGListIndexPathResult { + NSMapTable, NSIndexPath *> *_oldIndexPathMap; + NSMapTable, NSIndexPath *> *_newIndexPathMap; +} + +- (instancetype)initWithInserts:(NSArray *)inserts + deletes:(NSArray *)deletes + updates:(NSArray *)updates + moves:(NSArray *)moves + oldIndexPathMap:(NSMapTable, NSIndexPath *> *)oldIndexPathMap + newIndexPathMap:(NSMapTable, NSIndexPath *> *)newIndexPathMap { + if (self = [super init]) { + _inserts = [inserts copy]; + _deletes = [deletes copy]; + _updates = [updates copy]; + _moves = [moves copy]; + _oldIndexPathMap = [oldIndexPathMap copy]; + _newIndexPathMap = [newIndexPathMap copy]; + } + return self; +} + +- (BOOL)hasChanges { + return self.inserts.count || self.deletes.count || self.updates.count || self.moves.count; +} + +- (IGListIndexPathResult *)resultWithUpdatedMovesAsDeleteInserts { + NSMutableSet *deletes = [self.deletes mutableCopy]; + NSMutableSet *inserts = [self.inserts mutableCopy]; + NSMutableSet *filteredUpdates = [self.updates mutableCopy]; + + NSArray *moves = self.moves; + NSMutableArray *filteredMoves = [moves mutableCopy]; + + const NSUInteger moveCount = moves.count; + for (NSInteger i = moveCount - 1; i >= 0; i--) { + IGListMoveIndexPath *move = moves[i]; + if ([filteredUpdates containsObject:move.from]) { + [filteredMoves removeObjectAtIndex:i]; + [filteredUpdates removeObject:move.from]; + [deletes addObject:move.from]; + [inserts addObject:move.to]; + } + } + + return [[IGListIndexPathResult alloc] initWithInserts:[inserts allObjects] + deletes:[deletes allObjects] + updates:[filteredUpdates allObjects] + moves:filteredMoves + oldIndexPathMap:_oldIndexPathMap + newIndexPathMap:_newIndexPathMap]; +} + +- (NSIndexPath *)oldIndexPathForIdentifier:(id)identifier { + return [_oldIndexPathMap objectForKey:identifier]; +} + +- (NSIndexPath *)newIndexPathForIdentifier:(id)identifier { + return [_newIndexPathMap objectForKey:identifier]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@ %p; %zi inserts; %zi deletes; %zi updates; %zi moves>", + NSStringFromClass(self.class), self, self.inserts.count, self.deletes.count, self.updates.count, self.moves.count]; +} + +@end diff --git a/Source/IGListIndexSetResult.h b/Source/IGListIndexSetResult.h new file mode 100644 index 000000000..883f06034 --- /dev/null +++ b/Source/IGListIndexSetResult.h @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Result object returned when diffing with indexes. + */ +@interface IGListIndexSetResult : NSObject + +/** + Indexes inserted into the new collection. + */ +@property (nonatomic, strong, readonly) NSIndexSet *inserts; + +/** + Indexes deleted from the old collection. + */ +@property (nonatomic, strong, readonly) NSIndexSet *deletes; + +/** + Indexes in the new collection that need updated. + */ +@property (nonatomic, strong, readonly) NSIndexSet *updates; + +/** + Moves from an index in the old collection to an index in the new collection. + */ +@property (nonatomic, copy, readonly) NSArray *moves; + +/** + Convenience to query if the result has any changes. + + @return YES if the result has changes, NO otherwise. + */ +- (BOOL)hasChanges; + +/** + Fetch the index of the object with identifier before the diff. + + @param identifier The diff identifier of the object. See -[IGListDiffable diffIdentifier]. + + @return The index of the object before the diff, or NSNotFound. + */ +- (NSUInteger)oldIndexForIdentifier:(id)identifier; + +/** + Fetch the index of the object with identifier after the diff. + + @param identifier The diff identifier of the object. See -[IGListDiffable diffIdentifier]. + + @return The index of the object after the diff, or NSNotFound. + */ +- (NSUInteger)newIndexForIdentifier:(id)identifier; + +/** + Create a new result object transforming indexes that are both moved and updated into delete and inserts. + + @discussion This is a convenience method for using a result object to perform UICollectionView and UITableView updates. + */ +- (IGListIndexSetResult *)resultWithUpdatedMovesAsDeleteInserts; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListIndexSetResult.m b/Source/IGListIndexSetResult.m new file mode 100644 index 000000000..a898235b5 --- /dev/null +++ b/Source/IGListIndexSetResult.m @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListIndexSetResult.h" +#import "IGListIndexSetResultInternal.h" + +#import + +@implementation IGListIndexSetResult { + NSMapTable, NSNumber *> *_oldIndexMap; + NSMapTable, NSNumber *> *_newIndexMap; +} + +- (instancetype)initWithInserts:(NSIndexSet *)inserts + deletes:(NSIndexSet *)deletes + updates:(NSIndexSet *)updates + moves:(NSArray *)moves + oldIndexMap:(NSMapTable, NSNumber *> *)oldIndexMap + newIndexMap:(NSMapTable, NSNumber *> *)newIndexMap { + if (self = [super init]) { + _inserts = [inserts copy]; + _deletes = [deletes copy]; + _updates = [updates copy]; + _moves = [moves copy]; + _oldIndexMap = [oldIndexMap copy]; + _newIndexMap = [newIndexMap copy]; + } + return self; +} + +- (BOOL)hasChanges { + return self.inserts.count || self.deletes.count || self.updates.count || self.moves.count; +} + +- (IGListIndexSetResult *)resultWithUpdatedMovesAsDeleteInserts { + NSMutableIndexSet *deletes = [self.deletes mutableCopy]; + NSMutableIndexSet *inserts = [self.inserts mutableCopy]; + NSMutableIndexSet *filteredUpdates = [self.updates mutableCopy]; + + NSArray *moves = self.moves; + NSMutableArray *filteredMoves = [moves mutableCopy]; + + const NSUInteger moveCount = moves.count; + for (NSInteger i = moveCount - 1; i >= 0; i--) { + IGListMoveIndex *move = moves[i]; + if ([filteredUpdates containsIndex:move.from]) { + [filteredMoves removeObjectAtIndex:i]; + [filteredUpdates removeIndex:move.from]; + [deletes addIndex:move.from]; + [inserts addIndex:move.to]; + } + } + + return [[IGListIndexSetResult alloc] initWithInserts:inserts + deletes:deletes + updates:filteredUpdates + moves:filteredMoves + oldIndexMap:_oldIndexMap + newIndexMap:_newIndexMap]; +} + +- (NSUInteger)oldIndexForIdentifier:(id)identifier { + NSNumber *index = [_oldIndexMap objectForKey:identifier]; + return index == nil ? NSNotFound : [index integerValue]; +} + +- (NSUInteger)newIndexForIdentifier:(id)identifier { + NSNumber *index = [_newIndexMap objectForKey:identifier]; + return index == nil ? NSNotFound : [index integerValue]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@ %p; %zi inserts; %zi deletes; %zi updates; %zi moves>", + NSStringFromClass(self.class), self, self.inserts.count, self.deletes.count, self.updates.count, self.moves.count]; +} + +@end diff --git a/Source/IGListKit.h b/Source/IGListKit.h new file mode 100644 index 000000000..de02d6f89 --- /dev/null +++ b/Source/IGListKit.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +//! Project version number for IGListKit. +FOUNDATION_EXPORT double IGListKitVersionNumber; + +//! Project version string for IGListKit. +FOUNDATION_EXPORT const unsigned char IGListKitVersionString[]; + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/Source/IGListMacros.h b/Source/IGListMacros.h new file mode 100644 index 000000000..23c3b1ff7 --- /dev/null +++ b/Source/IGListMacros.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#ifndef IGLK_SUBCLASSING_RESTRICTED +#if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) +#define IGLK_SUBCLASSING_RESTRICTED __attribute__((objc_subclassing_restricted)) +#else +#define IGLK_SUBCLASSING_RESTRICTED +#endif // #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) +#endif // #ifndef IGLK_SUBCLASSING_RESTRICTED + +#ifndef IGLK_UNAVAILABLE +#define IGLK_UNAVAILABLE(message) __attribute__((unavailable(message))) +#endif // #ifndef IGLK_UNAVAILABLE + +#if IGLK_LOGGING_ENABLED +#define IGLKLog( s, ... ) do { NSLog( @"IGListKit: %@", [NSString stringWithFormat: (s), ##__VA_ARGS__] ); } while(0) +#else +#define IGLKLog( s, ... ) +#endif diff --git a/Source/IGListMoveIndex.h b/Source/IGListMoveIndex.h new file mode 100644 index 000000000..ab29fa747 --- /dev/null +++ b/Source/IGListMoveIndex.h @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + An object representing a move between indexes. + */ +@interface IGListMoveIndex : NSObject + +/** + An index in the old collection. + */ +@property (nonatomic, assign, readonly) NSUInteger from; + +/** + An index in the new collection. + */ +@property (nonatomic, assign, readonly) NSUInteger to; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListMoveIndex.m b/Source/IGListMoveIndex.m new file mode 100644 index 000000000..1fe86a921 --- /dev/null +++ b/Source/IGListMoveIndex.m @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListMoveIndex.h" +#import "IGListMoveIndexInternal.h" + +@implementation IGListMoveIndex + +- (instancetype)initWithFrom:(NSUInteger)from to:(NSUInteger)to { + if (self = [super init]) { + _from = from; + _to = to; + } + return self; +} + +- (NSUInteger)hash { + return _from ^ _to; +} + +- (BOOL)isEqual:(id)object { + if (object == self) { + return YES; + } + if ([object isKindOfClass:[IGListMoveIndex class]]) { + NSUInteger f1 = self.from, f2 = [object from]; + NSUInteger t1 = self.to, t2 = [object to]; + return f1 == f2 && t1 == t2; + } + return NO; +} + +- (NSComparisonResult)compare:(id)object { + const NSUInteger right = [object from]; + const NSUInteger left = [self from]; + if (left == right) { + return NSOrderedSame; + } else if (left < right) { + return NSOrderedAscending; + } else { + return NSOrderedDescending; + } +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@ %p; from: %zi; to: %zi;>", NSStringFromClass(self.class), self, self.from, self.to]; +} + +@end diff --git a/Source/IGListMoveIndexPath.h b/Source/IGListMoveIndexPath.h new file mode 100644 index 000000000..45bbb1024 --- /dev/null +++ b/Source/IGListMoveIndexPath.h @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + An object representing a move between indexes. + */ +@interface IGListMoveIndexPath : NSObject + +/** + An index path in the old collection. + */ +@property (nonatomic, strong, readonly) NSIndexPath *from; + +/** + An index path in the new collection. + */ +@property (nonatomic, strong, readonly) NSIndexPath *to; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListMoveIndexPath.m b/Source/IGListMoveIndexPath.m new file mode 100644 index 000000000..dacb8480f --- /dev/null +++ b/Source/IGListMoveIndexPath.m @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListMoveIndexPath.h" +#import "IGListMoveIndexPathInternal.h" + +#import + +@implementation IGListMoveIndexPath + +- (instancetype)initWithFrom:(NSIndexPath *)from to:(NSIndexPath *)to { + NSParameterAssert(from != nil); + NSParameterAssert(to != nil); + if (self = [super init]) { + _from = from; + _to = to; + } + return self; +} + +- (NSUInteger)hash { + return [_from hash] ^ [_to hash]; +} + +- (BOOL)isEqual:(id)object { + if (object == self) { + return YES; + } + if ([object isKindOfClass:[IGListMoveIndexPath class]]) { + NSIndexPath *f1 = self.from, *f2 = [object from]; + NSIndexPath *t1 = self.to, *t2 = [object to]; + return [f1 isEqual:f2] && [t1 isEqual:t2]; + } + return NO; +} + +- (NSComparisonResult)compare:(id)object { + return [[self from] compare:[object from]]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@ %p; from: %@; to: %@;>", NSStringFromClass(self.class), self, self.from, self.to]; +} + +@end diff --git a/Source/IGListReloadDataUpdater.h b/Source/IGListReloadDataUpdater.h new file mode 100644 index 000000000..bbfa4be40 --- /dev/null +++ b/Source/IGListReloadDataUpdater.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import + +IGLK_SUBCLASSING_RESTRICTED +@interface IGListReloadDataUpdater : NSObject + +@end diff --git a/Source/IGListReloadDataUpdater.m b/Source/IGListReloadDataUpdater.m new file mode 100644 index 000000000..9dac153d9 --- /dev/null +++ b/Source/IGListReloadDataUpdater.m @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@implementation IGListReloadDataUpdater + +#pragma mark - IGListUpdatingDelegate + +- (NSPointerFunctions *)objectLookupPointerFunctions { + return [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsObjectPersonality]; +} + +- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView + fromObjects:(NSArray *)fromObjects + toObjects:(NSArray *)toObjects + animated:(BOOL)animated + objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock + completion:(IGListUpdatingCompletion)completion { + objectTransitionBlock(toObjects); + [self synchronousReloadDataWithCollectionView:collectionView]; + if (completion) { + completion(YES); + } +} + +- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView + animated:(BOOL)animated + itemUpdates:(IGListItemUpdateBlock)itemUpdates + completion:(IGListUpdatingCompletion)completion { + itemUpdates(); + [self synchronousReloadDataWithCollectionView:collectionView]; + if (completion) { + completion(YES); + } +} + +- (void)insertItemsIntoCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { + [self synchronousReloadDataWithCollectionView:collectionView]; +} + +- (void)deleteItemsFromCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { + [self synchronousReloadDataWithCollectionView:collectionView]; +} + +- (void)reloadItemsInCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { + [self synchronousReloadDataWithCollectionView:collectionView]; +} + +- (void)reloadDataWithCollectionView:(UICollectionView *)collectionView reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock completion:(IGListUpdatingCompletion)completion { + reloadUpdateBlock(); + [self synchronousReloadDataWithCollectionView:collectionView]; + if (completion) { + completion(YES); + } +} + +- (void)reloadCollectionView:(UICollectionView *)collectionView sections:(NSIndexSet *)sections { + [self synchronousReloadDataWithCollectionView:collectionView]; +} + +- (void)synchronousReloadDataWithCollectionView:(UICollectionView *)collectionView { + [collectionView reloadData]; + [collectionView layoutIfNeeded]; +} + +@end diff --git a/Source/IGListScrollDelegate.h b/Source/IGListScrollDelegate.h new file mode 100644 index 000000000..d13576f12 --- /dev/null +++ b/Source/IGListScrollDelegate.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class IGListAdapter; +@class IGListSectionController; + +@protocol IGListSectionType; + +/** + Implement this protocol to receive display events for an section controller when it is on screen. + */ +@protocol IGListScrollDelegate + +/** + Tells the delegate that the section controller was scrolled on screen. + + @param listAdapter The list adapter whose collection view was scrolled. + @param sectionController The visible section controller that was scrolled. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter didScrollSectionController:(IGListSectionController *)sectionController; + +/** + Tells the delegate that the section controller will be dragged on screen. + + @param listAdapter The list adapter whose collection view will drag. + @param sectionController The visible section controller that will drag. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter willBeginDraggingSectionController:(IGListSectionController *)sectionController; + +/** + Tells the delegate that the section controller did end dragging on screen. + + @param listAdapter The list adapter whose collection view ended dragging. + @param sectionController The visible section controller that ended dragging. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDraggingSectionController:(IGListSectionController *)sectionController willDecelerate:(BOOL)decelerate; + +@end diff --git a/Source/IGListSectionController.h b/Source/IGListSectionController.h new file mode 100644 index 000000000..61f8799b7 --- /dev/null +++ b/Source/IGListSectionController.h @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import +#import +#import +#import + +/** + The base class for section controllers used in the list infra. This class is meant to be subclassed. + */ +@interface IGListSectionController : NSObject + +/** + The view controller housing the adapter that created this section controller. + + @discussion Use this view controller to push, pop, present, or do other custom transitions. It is considered very bad + practice to cast this to a known view controller and call methods on it other than for navigations and transitions. + */ +@property (nonatomic, weak, nullable, readonly) UIViewController *viewController; + +/** + A context object for interacting with the collection i.e. accessing the collection size, dequeing cells, + reloading/inserting/deleting, etc. + */ +@property (nonatomic, weak, nullable, readonly) id collectionContext; + +/** + The margins used to lay out content in the section controller. + + @see -[UICollectionViewFlowLayout sectionInset] + */ +@property (nonatomic, assign) UIEdgeInsets inset; + +/** + The minimum spacing to use between rows of items. + + @see -[UICollectionViewFlowLayout minimumLineSpacing] + */ +@property (nonatomic, assign) CGFloat minimumLineSpacing; + +/** + The minimum spacing to use between items in the same row. + + @see -[UICollectionViewFlowLayout minimumInteritemSpacing] + */ +@property (nonatomic, assign) CGFloat minimumInteritemSpacing; + +/** + The supplementary view source for the section controller. Can be nil. + + @return An object that conforms to IGListSupplementaryViewSource or nil. + + @discussion You may wish to return self if your section controller implements this protocol. + */ +@property (nonatomic, weak, nullable) id supplementaryViewSource; + +/** + An object that handles display events for the section controller. Can be nil. + + @return An object that conforms to IGListDisplayDelegate or nil. + + @discussion You may wish to return self if your section controller implements this protocol. + */ +@property (nonatomic, weak, nullable) id displayDelegate; + +/** + An object that handles working range events for the section controller. Can be nil. + + @return An object that conforms to IGListWorkingRangeDelegate or nil. + + @discussion You may wish to return self if your section controller implements this protocol. + */ +@property (nonatomic, weak, nullable) id workingRangeDelegate; + +/** + An object that handles display events for the section controller. Can be nil. + + @return An object that conforms to IGListDisplayDelegate or nil. + + @discussion You may wish to return self if your section controller implements this protocol. + */ +@property (nonatomic, weak, nullable) id scrollDelegate; + +@end diff --git a/Source/IGListSectionController.m b/Source/IGListSectionController.m new file mode 100644 index 000000000..5e36b689f --- /dev/null +++ b/Source/IGListSectionController.m @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListSectionControllerInternal.h" + +#import +#import + +static NSString * const kIGListSectionControllerThreadKey = @"kIGListSectionControllerThreadKey"; + +@interface IGListSectionControllerThreadContext : NSObject +@property (nonatomic, weak) UIViewController *viewController; +@property (nonatomic, weak) id collectionContext; +@end +@implementation IGListSectionControllerThreadContext +@end + +static NSMutableArray *threadContextStack(void) { + IGAssertMainThread(); + NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary]; + NSMutableArray *stack = threadDictionary[kIGListSectionControllerThreadKey]; + if (stack == nil) { + stack = [NSMutableArray new]; + threadDictionary[kIGListSectionControllerThreadKey] = stack; + } + return stack; +} + +void IGListSectionControllerPushThread(UIViewController *viewController, id collectionContext) { + IGListSectionControllerThreadContext *context = [IGListSectionControllerThreadContext new]; + context.viewController = viewController; + context.collectionContext = collectionContext; + + [threadContextStack() addObject:context]; +} + +void IGListSectionControllerPopThread(void) { + NSMutableArray *stack = threadContextStack(); + IGAssert(stack.count > 0, @"IGListSectionController thread stack is empty"); + [stack removeLastObject]; +} + +@implementation IGListSectionController + +- (instancetype)init { + if (self = [super init]) { + IGListSectionControllerThreadContext *context = [threadContextStack() lastObject]; + _viewController = context.viewController; + _collectionContext = context.collectionContext; + + if (_collectionContext == nil) { + IGLKLog(@"Warning: Creating %@ outside of -[IGListAdapterDataSource listAdapter:sectionControllerForObject:]. Collection context and view controller will be set later.", + NSStringFromClass([self class])); + } + + _minimumInteritemSpacing = 0.0; + _minimumLineSpacing = 0.0; + _inset = UIEdgeInsetsZero; + } + return self; +} + +@end diff --git a/Source/IGListSectionType.h b/Source/IGListSectionType.h new file mode 100644 index 000000000..62a3dbc75 --- /dev/null +++ b/Source/IGListSectionType.h @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@protocol IGListSupplementaryViewSource; +@protocol IGListDisplayDelegate; +@protocol IGListWorkingRangeDelegate; + +NS_ASSUME_NONNULL_BEGIN + +/** + Implement this protocol in order to be used within the IGListKit data infrastructure and be registered for use in an + IGListAdapter. An IGListSectionType conforming object represents a single instance of an object in a collection of + objects. + + The infrastructure uses each IGListSectionType conforming object as a "view model" to populate and control cells as + part of a section in a UICollectionView feed. IGListSectionType objects should be architected without knowledge of + "global" state of the feed they are contained in. + + Index paths are used as a convenience for communicating the section index to each section object without allowing each + section to mutate its own position within a feed. The row of an index path can be directly mapped to a cell within + an IGListSectionType conforming object. + */ +@protocol IGListSectionType + +/** + The number of items in the IGListSectionType. + + @return A count of items in the list. + + @discussion The count returned is used to drive the number of cells displayed for this list. You are free to change + this value between data loading passes. + */ +- (NSInteger)numberOfItems; + +/** + The specific size for the item at the specified index. + + @param index The row index of the item. + + @return The size for the item at index. + + @discussion The returned size is not garaunteed to be used. The feed implementation may query list items for their + layout information at will, or use its own layout metrics. For example, consider a dynamic-text sized feed vs. a fixed + height-and-width grid feed. The former will ask each IGListSectionType for a size, and the latter will likely not. + */ +- (CGSize)sizeForItemAtIndex:(NSInteger)index; + +/** + Asks the section controller for a fully configured cell at an index path. + + @param index The index of the requested row. + + @return A configured UICollectionViewCell subclass. + + @discussion This is your opportunity to do any cell setup and configuration. The infrastructure requests a cell when it + will be used on screen. You should never allocate new cells in this method, instead on the provided adapter call + -dequeCellClass:forIndexPath: which either deques a cell from the UICollectionView or creates a new one for you. + */ +- (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index; + +/** + Tells the IGListSectionType that the section controller was updated to a new item. + + @param object The object mapped to this section controller. + + @discussion When this method is called, all available contexts and configurations have been set for the section + controller. Also, depending on the updating strategy used, your item models may have changed objects in memory, so you + can use this event to update the object stored on your section controller. + + This method will only be called when the object instance has changed, either from nil or a previous object. + */ +- (void)didUpdateToObject:(id)object; + +/** + Tells the IGListSectionType that the cell at the specified index path was selected. + + @param index The index of the selected cell. + + @discussion Implementation of this method is required for compile-time safety, but you are free to do nothing. + */ +- (void)didSelectItemAtIndex:(NSInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListSingleSectionController.h b/Source/IGListSingleSectionController.h new file mode 100644 index 000000000..71a724f37 --- /dev/null +++ b/Source/IGListSingleSectionController.h @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class IGListSingleSectionController; + +/** + A delegate that can receive selection events on an IGListSingleSectionController. + */ +@protocol IGListSingleSectionControllerDelegate + +/** + Tells the delegate that the section controller was selected. + + @param sectionController The section controller that was selected. + */ +- (void)didSelectSingleSectionController:(IGListSingleSectionController *)sectionController; + +@end + +/** + This section controller is meant to make building simple, single-cell feeds easier. By providing the type of cell, a block + to configure the cell, and a block to return the size of a cell, you can use an IGListAdapter-powered feed without + overcomplicating your architecture. + */ +IGLK_SUBCLASSING_RESTRICTED +@interface IGListSingleSectionController : IGListSectionController + +/** + Create a new section controller for a given cell type that will always have only one cell when present in a feed. + + @param cellClass The UICollectionViewCell subclass for the single cell. + @param configureBlock A block that configures the cell with the item given to the section controller. + @param sizeBlock A block that returns the size for the cell given the collection context. + + @return A new section controller. + + @warning Be VERY CAREFUL not to create retain cycles by holding strong references to: the object that owns the adapter + (usually "self") or the IGListAdapter. Pass in locally scoped objects or use weak references! + */ +- (instancetype)initWithCellClass:(Class)cellClass + configureBlock:(void (^)(id item, __kindof UICollectionViewCell *cell))configureBlock + sizeBlock:(CGSize (^)(id collectionContext))sizeBlock NS_DESIGNATED_INITIALIZER; + +/** + An optional delegate that handles selection and deselection. + */ +@property (nonatomic, weak, nullable) id selectionDelegate; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListSingleSectionController.m b/Source/IGListSingleSectionController.m new file mode 100644 index 000000000..8087156d8 --- /dev/null +++ b/Source/IGListSingleSectionController.m @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListSingleSectionController.h" + +#import + +@interface IGListSingleSectionController () + +@property (nonatomic, strong, readonly) Class cellClass; +@property (nonatomic, strong, readonly) void (^configureBlock)(id, __kindof UICollectionViewCell *); +@property (nonatomic, strong, readonly) CGSize (^sizeBlock)(id); + +@property (nonatomic, strong) id item; + +@end + +@implementation IGListSingleSectionController + +- (instancetype)initWithCellClass:(Class)cellClass + configureBlock:(void (^)(id, __kindof UICollectionViewCell *))configureBlock + sizeBlock:(CGSize (^)(id))sizeBlock { + IGParameterAssert(cellClass != nil); + IGParameterAssert(configureBlock != nil); + if (self = [super init]) { + _cellClass = cellClass; + _configureBlock = [configureBlock copy]; + _sizeBlock = [sizeBlock copy]; + } + return self; +} + +#pragma mark - IGListSectionType + +- (NSInteger)numberOfItems { + return 1; +} + +- (CGSize)sizeForItemAtIndex:(NSInteger)index { + return self.sizeBlock(self.collectionContext); +} + +- (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { + IGParameterAssert(index == 0); + id cell = [self.collectionContext dequeueReusableCellOfClass:self.cellClass forSectionController:self atIndex:index]; + self.configureBlock(self.item, cell); + return cell; +} + +- (void)didUpdateToObject:(id)object { + self.item = object; +} + +- (void)didSelectItemAtIndex:(NSInteger)index { + [self.selectionDelegate didSelectSingleSectionController:self]; +} + +@end diff --git a/Source/IGListStackedSectionController.h b/Source/IGListStackedSectionController.h new file mode 100644 index 000000000..c98067759 --- /dev/null +++ b/Source/IGListStackedSectionController.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import +#import + +/** + This is a clustered section controller, composed of many child section controllers. It constructs and routes item-level + indexes to the appropriate child section controller with a local index. This lets you build section controllers made up + of individual units that can be shared and reused with other section controllers. + + For example, you can create a "Comments" section controller that displays lists of text that is used alongside photo, + video, or slideshow section controllers. You then have four small and manageable section controllers instead of one + huge class. + */ +IGLK_SUBCLASSING_RESTRICTED +@interface IGListStackedSectionController : IGListSectionController + +/** + Create a new stacked section controller. + + @param sectionControllers An array of section controllers that make up the stack. + + @discussion The order of the section controllers dictates the order in which they appear. The first section controller + that is the supplementary source decides which supplementary views get displayed. + */ +- (instancetype)initWithSectionControllers:(NSArray *> *)sectionControllers NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end diff --git a/Source/IGListStackedSectionController.m b/Source/IGListStackedSectionController.m new file mode 100644 index 000000000..1b310d996 --- /dev/null +++ b/Source/IGListStackedSectionController.m @@ -0,0 +1,301 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListStackedSectionControllerInternal.h" + +#import + +#import +#import + +#import "IGListSectionControllerInternal.h" + +@interface UICollectionViewCell (IGListStackedSectionController) +@end +@implementation UICollectionViewCell (IGListStackedSectionController) + +static void * kStackedSectionControllerKey = &kStackedSectionControllerKey; + +- (void)ig_setStackedSectionController:(id)stackedSectionController { + objc_setAssociatedObject(self, kStackedSectionControllerKey, stackedSectionController, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (id)ig_stackedSectionController { + return objc_getAssociatedObject(self, kStackedSectionControllerKey); +} + +static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerIndexKey; + +- (void)ig_setStackedSectionControllerIndex:(NSInteger)stackedSectionControllerIndex { + objc_setAssociatedObject(self, kStackedSectionControllerIndexKey, @(stackedSectionControllerIndex), OBJC_ASSOCIATION_ASSIGN); +} + +- (NSInteger)ig_stackedSectionControllerIndex { + return [objc_getAssociatedObject(self, kStackedSectionControllerIndexKey) integerValue]; +} + +@end + +@implementation IGListStackedSectionController + +- (instancetype)initWithSectionControllers:(NSArray *> *)sectionControllers { + if (self = [super init]) { + for (IGListSectionController *sectionController in sectionControllers) { + sectionController.collectionContext = self; + sectionController.viewController = self.viewController; + + if (self.supplementaryViewSource == nil) { + self.supplementaryViewSource = sectionController.supplementaryViewSource; + } + } + + _visibleSectionControllers = [[NSCountedSet alloc] init]; + _sectionControllers = [NSOrderedSet orderedSetWithArray:sectionControllers]; + + self.displayDelegate = self; + self.scrollDelegate = self; + + [self reloadData]; + } + return self; +} + + +#pragma mark - Private API + +- (void)reloadData { + NSMutableArray *sectionControllers = [[NSMutableArray alloc] init]; + NSMutableArray *offsets = [[NSMutableArray alloc] init]; + + NSUInteger numberOfItems = 0; + for (IGListSectionController *sectionController in self.sectionControllers) { + [offsets addObject:@(numberOfItems)]; + + const NSUInteger items = [sectionController numberOfItems]; + for (NSUInteger i = 0; i < items; i++) { + [sectionControllers addObject:sectionController]; + } + + numberOfItems += items; + } + + self.sectionControllerOffsets = offsets; + self.flattenedNumberOfItems = numberOfItems; + self.sectionControllersForItems = sectionControllers; + + IGAssert(self.sectionControllerOffsets.count == self.sectionControllers.count, @"Not enough offsets for section controllers"); + IGAssert(self.sectionControllersForItems.count == self.flattenedNumberOfItems, @"Controller map does not equal total number of items"); +} + +- (IGListSectionController *)sectionControllerForObjectIndex:(NSUInteger)itemIndex { + return self.sectionControllersForItems[itemIndex]; +} + +- (NSUInteger)offsetForSectionController:(IGListSectionController *)sectionController { + const NSUInteger index = [self.sectionControllers indexOfObject:sectionController]; + IGAssert(index != NSNotFound, @"Querying offset for an undocumented section controller"); + return [self.sectionControllerOffsets[index] integerValue]; +} + +- (NSUInteger)localIndexForSectionController:(IGListSectionController *)sectionController index:(NSUInteger)index { + const NSUInteger offset = [self offsetForSectionController:sectionController]; + IGAssert(offset <= index, @"Section controller offset must be less than or equal to the item index"); + return index - offset; +} + +- (NSIndexSet *)itemIndexesForSectionController:(IGListSectionController *)sectionController indexes:(NSIndexSet *)indexes { + const NSUInteger offset = [self offsetForSectionController:sectionController]; + NSMutableIndexSet *itemIndexes = [[NSMutableIndexSet alloc] init]; + [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [itemIndexes addIndex:(idx + offset)]; + }]; + return itemIndexes; +} + + +#pragma mark - IGListSectionType + +- (NSInteger)numberOfItems { + return self.flattenedNumberOfItems; +} + +- (CGSize)sizeForItemAtIndex:(NSInteger)index { + IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index]; + const NSUInteger localIndex = [self localIndexForSectionController:sectionController index:index]; + return [sectionController sizeForItemAtIndex:localIndex]; +} + +- (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { + IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index]; + const NSUInteger localIndex = [self localIndexForSectionController:sectionController index:index]; + return [sectionController cellForItemAtIndex:localIndex]; +} + +- (void)didUpdateToObject:(id)object { + for (IGListSectionController *sectionController in self.sectionControllers) { + [sectionController didUpdateToObject:object]; + } +} + +- (void)didSelectItemAtIndex:(NSInteger)index { + IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index]; + const NSUInteger localIndex = [self localIndexForSectionController:sectionController index:index]; + [sectionController didSelectItemAtIndex:localIndex]; +} + +#pragma mark - IGListCollectionContext + +- (CGSize)containerSize { + return [self.collectionContext containerSize]; +} + +- (NSUInteger)indexForCell:(UICollectionViewCell *)cell sectionController:(IGListSectionController *)sectionController { + const NSUInteger index = [self.collectionContext indexForCell:cell sectionController:self]; + return [self localIndexForSectionController:sectionController index:index]; +} + +- (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController { + const NSUInteger offset = [self offsetForSectionController:sectionController]; + return [self.collectionContext cellForItemAtIndex:(index + offset) sectionController:self]; +} + +- (NSArray *)visibleCellsForSectionController:(IGListSectionController *)sectionController { + NSMutableArray *cells = [NSMutableArray new]; + id collectionContext = self.collectionContext; + NSArray *visibleCells = [collectionContext visibleCellsForSectionController:self]; + for (UICollectionViewCell *cell in visibleCells) { + const NSUInteger index = [collectionContext indexForCell:cell sectionController:self]; + if (self.sectionControllersForItems[index] == sectionController) { + [cells addObject:cell]; + } + } + return [cells copy]; +} + +- (void)deselectItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController animated:(BOOL)animated { + const NSUInteger localIndex = [self localIndexForSectionController:sectionController index:index]; + [self.collectionContext deselectItemAtIndex:localIndex sectionController:self animated:animated]; +} + +- (NSUInteger)sectionForSectionController:(IGListSectionController *)sectionController { + return [self.collectionContext sectionForSectionController:self]; +} + +- (UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index { + const NSUInteger offset = [self offsetForSectionController:sectionController]; + return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableCellOfClass:cellClass + forSectionController:self + atIndex:(index + offset)]; +} + +- (UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind + forSectionController:(IGListSectionController *)sectionController + class:(Class)viewClass + atIndex:(NSInteger)index { + const NSUInteger offset = [self offsetForSectionController:sectionController]; + return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableSupplementaryViewOfKind:elementKind + forSectionController:self + class:viewClass + atIndex:(index + offset)]; +} + +- (void)reloadInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { + NSIndexSet *itemIndexes = [self itemIndexesForSectionController:sectionController indexes:indexes]; + [self.collectionContext reloadInSectionController:self atIndexes:itemIndexes]; +} + +- (void)insertInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { + [self reloadData]; + NSIndexSet *itemIndexes = [self itemIndexesForSectionController:sectionController indexes:indexes]; + [self.collectionContext insertInSectionController:self atIndexes:itemIndexes]; +} + +- (void)deleteInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { + [self reloadData]; + NSIndexSet *itemIndexes = [self itemIndexesForSectionController:sectionController indexes:indexes]; + [self.collectionContext deleteInSectionController:self atIndexes:itemIndexes]; +} + +- (void)reloadSectionController:(IGListSectionController *)sectionController { + [self reloadData]; + [self.collectionContext reloadSectionController:self]; +} + +- (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion { + [self.collectionContext performBatchAnimated:animated updates:^{ + updates(); + } completion:^(BOOL finished) { + [self reloadData]; + if (completion) { + completion(finished); + } + }]; +} + + +#pragma mark - IGListDisplayDelegate + +- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController cell:(UICollectionViewCell *)cell atIndex:(NSInteger)index { + IGListSectionController *childSectionController = [self sectionControllerForObjectIndex:index]; + const NSUInteger localIndex = [self localIndexForSectionController:childSectionController index:index]; + + [cell ig_setStackedSectionController:childSectionController]; + [cell ig_setStackedSectionControllerIndex:localIndex]; + + NSCountedSet *visibleSectionControllers = self.visibleSectionControllers; + id displayDelegate = [childSectionController displayDelegate]; + + if ([visibleSectionControllers countForObject:childSectionController] == 0) { + [displayDelegate listAdapter:listAdapter willDisplaySectionController:childSectionController]; + } + [displayDelegate listAdapter:listAdapter willDisplaySectionController:childSectionController cell:cell atIndex:localIndex]; + + [visibleSectionControllers addObject:childSectionController]; +} + +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController cell:(UICollectionViewCell *)cell atIndex:(NSInteger)index { + IGListSectionController *childSectionController = [self sectionControllerForObjectIndex:index]; + const NSUInteger localIndex = [self localIndexForSectionController:childSectionController index:index]; + NSCountedSet *visibleSectionControllers = self.visibleSectionControllers; + id displayDelegate = [childSectionController displayDelegate]; + + [displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:childSectionController cell:cell atIndex:localIndex]; + + [visibleSectionControllers removeObject:childSectionController]; + if ([visibleSectionControllers countForObject:childSectionController] == 0) { + [displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:childSectionController]; + } +} + +- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController {} +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController {} + +#pragma mark - IGListScrollDelegate + +- (void)listAdapter:(IGListAdapter *)listAdapter didScrollSectionController:(IGListSectionController *)sectionController { + for (IGListSectionController *childSectionController in self.sectionControllers) { + [[childSectionController scrollDelegate] listAdapter:listAdapter didScrollSectionController:childSectionController]; + } +} + +- (void)listAdapter:(IGListAdapter *)listAdapter willBeginDraggingSectionController:(IGListSectionController *)sectionController { + for (IGListSectionController *childSectionController in self.sectionControllers) { + [[childSectionController scrollDelegate] listAdapter:listAdapter willBeginDraggingSectionController:sectionController]; + } +} + +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDraggingSectionController:(IGListSectionController *)sectionController willDecelerate:(BOOL)decelerate { + for (IGListSectionController *childSectionController in self.sectionControllers) { + [[childSectionController scrollDelegate] listAdapter:listAdapter didEndDraggingSectionController:childSectionController willDecelerate:decelerate]; + } +} + +@end diff --git a/Source/IGListSupplementaryViewSource.h b/Source/IGListSupplementaryViewSource.h new file mode 100644 index 000000000..5df2de64d --- /dev/null +++ b/Source/IGListSupplementaryViewSource.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Implement the methods of this protocol to provide information about a list's supplementary views. This data is used in + IGListAdapter which then configures and maintains a UICollectionView. The supplementary API reflects that in + UICollectionView, UICollectionViewLayout, and UICollectionViewDataSource. + */ +@protocol IGListSupplementaryViewSource + +/** + Asks the SupplementaryViewSource for an array of supported element kinds. + + @return An array of element kind strings that the supplementary source handles. + */ +- (NSArray *)supportedElementKinds; + +/** + Asks the SupplementaryViewSource for a configured supplementary view for the specified kind and index. + + @param elementKind The kind of supplementary view being requested + @param index The index for the row being requested. + + @discussion This is your opportunity to do any supplementary view setup and configuration. + + @warning You should never allocate new views in this method. Instead deque a view from the `IGListCollectionContext`. + */ +- (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind + atIndex:(NSInteger)index; + +/** + Asks the SupplementaryViewSource for the size of a supplementary view for the given kind and index path. + + @param elementKind The kind of supplementary view. + @param index The index of the requested row. + + @return The size for the supplementary view. + */ +- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind + atIndex:(NSInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListUpdatingDelegate.h b/Source/IGListUpdatingDelegate.h new file mode 100644 index 000000000..67b78616e --- /dev/null +++ b/Source/IGListUpdatingDelegate.h @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@protocol IGListDiffable; + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^IGListUpdatingCompletion)(BOOL finished); +typedef void (^IGListObjectTransitionBlock)(NSArray *toObjects); +typedef void (^IGListItemUpdateBlock)(); +typedef void (^IGListReloadUpdateBlock)(); + +/** + Implement this protocol in order to handle both section and row based update events. Implementation should forward or + coalesce these events to a backing store or collection. + */ +@protocol IGListUpdatingDelegate + +/** + Asks the delegate for the pointer functions for looking up an object in a collection. + + @return Pointer functions for looking up an object in a collection. + + @discussion Since the updating delegate is responsible for transitioning between object sets, it becomes the "source of + truth" for how objects and their corresponding section controllers are mapped. This allows the updater to control if + objects are looked up by pointer, or more traditionally, with hash/isEqual. + + For behavior similar to NSDictionary, simply return + +[NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsObjectPersonality]. + */ +- (NSPointerFunctions *)objectLookupPointerFunctions; + +/** + Tells the delegate to perform a section transition from an old array of objects to a new one. + + @param collectionView The collection view to perform the transition on. + @param fromObjects The previous objects in the collection view. Objects must conform to IGListDiffable. + @param toObjects The new objects in collection view. Objects must conform to IGListDiffable. + @param animated A flag indicating if the transition should be animated. + @param objectTransitionBlock A block that must be called when the adapter applies changes to the collection view. + @param completion A completion block to execute when the update is finished. + + @discussion Implementations determine how to transition between objects. You can perform a diff on the objects, reload + each section, or simply call -reloadData on the collection view. In the end, the collection view must be setup with a + section for each object in the toObjects array. + + The `objectUpdateBlock` block should be called prior to making any UICollectionView updates, passing in the `toObjects` + that the updater is applying. + */ +- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView + fromObjects:(nullable NSArray> *)fromObjects + toObjects:(nullable NSArray> *)toObjects + animated:(BOOL)animated + objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock + completion:(nullable IGListUpdatingCompletion)completion; + +/** + Tells the delegate to perform item inserts at the given index paths. + + @param collectionView The collection view to perform the transition on. + @param indexPaths The index paths to insert items into. + */ +- (void)insertItemsIntoCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths; + +/** + Tells the delegate to perform item deletes at the given index paths. + + @param collectionView The collection view to perform the transition on. + @param indexPaths The index paths to delete items from. + */ +- (void)deleteItemsFromCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths; + +/** + Tells the delegate to perform item reloads at the given index paths. + + @param collectionView The collection view to perform the transition on. + @param indexPaths The index paths of items to reload. + */ +- (void)reloadItemsInCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths; + +/** + Completely reload data in the collection. + + @param collectionView The collection view to reload. + @param reloadUpdateBlock A block that must be called when the adapter reloads the collection view. + @param completion A completion block to execute when the reload is finished. + */ +- (void)reloadDataWithCollectionView:(UICollectionView *)collectionView + reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock + completion:(nullable IGListUpdatingCompletion)completion; + +/** + Completely reload each section in the collection view. + + @param collectionView The collection view to reload. + @param sections The sections to reload. + */ +- (void)reloadCollectionView:(UICollectionView *)collectionView sections:(NSIndexSet *)sections; + +/** + Perform an item update block in the collection view. + + @param collectionView The collection view to update. + @param animated A flag indicating if the transition should be animated. + @param itemUpdates A block containing all of the updates. + @param completion A completion block to execute when the update is finished. + */ +- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView + animated:(BOOL)animated + itemUpdates:(IGListItemUpdateBlock)itemUpdates + completion:(nullable IGListUpdatingCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListWorkingRangeDelegate.h b/Source/IGListWorkingRangeDelegate.h new file mode 100644 index 000000000..4621fceb4 --- /dev/null +++ b/Source/IGListWorkingRangeDelegate.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class IGListAdapter; +@class IGListSectionController; + +@protocol IGListSectionType; + +NS_ASSUME_NONNULL_BEGIN + +/** + Implement this protocol to receive working range events for a list. + + The working range is a range *near* the viewport in which you can begin preparing content for display. For example, + you could begin decoding images, or warming text caches. + */ +@protocol IGListWorkingRangeDelegate + +/** + Notifies the delegate that an section controller will enter the working range. + + @param listAdapter The adapter controlling the feed. + @param sectionController The section controller entering the range. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter sectionControllerWillEnterWorkingRange:(IGListSectionController *)sectionController; + +/** + Notifies the delegate that an section controller exited the working range. + + @param listAdapter The adapter controlling the feed. + @param sectionController The section controller that exited the range. + */ +- (void)listAdapter:(IGListAdapter *)listAdapter sectionControllerDidExitWorkingRange:(IGListSectionController *)sectionController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Info.plist b/Source/Info.plist new file mode 100644 index 000000000..20b05ecd4 --- /dev/null +++ b/Source/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Source/Internal/IGListAdapterInternal.h b/Source/Internal/IGListAdapterInternal.h new file mode 100644 index 000000000..3dd217abf --- /dev/null +++ b/Source/Internal/IGListAdapterInternal.h @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import "IGListAdapterProxy.h" +#import "IGListDisplayHandler.h" +#import "IGListSectionMap.h" +#import "IGListWorkingRangeHandler.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface IGListAdapter () +< +UICollectionViewDataSource, +UICollectionViewDelegateFlowLayout, +IGListCollectionContext +> +{ + __weak UICollectionView *_collectionView; +} + +@property (nonatomic, strong, readonly) id updatingDelegate; +@property (nonatomic, strong, readonly) IGListSectionMap *sectionMap; +@property (nonatomic, strong, readonly) IGListDisplayHandler *displayHandler; +@property (nonatomic, strong, readonly) IGListWorkingRangeHandler *workingRangeHandler; + +@property (nonatomic, strong, nullable) IGListAdapterProxy *delegateProxy; + +@property (nonatomic, strong, nullable) UIView *emptyBackgroundView; + +/** + When making object updates inside a batch update block, delete operations must use the section /before/ any moves take + place. This includes when other objects are deleted or inserted ahead of the section controller making the mutations. + In order to account for this we must track when the adapter is in the middle of an update block as well as the section + controller mapping prior to the transition. + + Note that the previous section controller map is destroyed as soon as a transition is finished so there is no dangling + objects or section controllers. + */ +@property (nonatomic, assign) BOOL isInUpdateBlock; +@property (nonatomic, strong, nullable) IGListSectionMap *previoussectionMap; + +@property (nonatomic, strong) NSMutableSet *registeredCellClasses; +@property (nonatomic, strong) NSMutableSet *registeredSupplementaryViewIdentifiers; + +- (NSArray *)indexPathsFromSectionController:(IGListSectionController *)sectionController + indexes:(NSIndexSet *)indexes + adjustForUpdateBlock:(BOOL)adjustForUpdateBlock; + +- (NSString *)reusableViewIdentifierForClass:(Class)viewClass; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListAdapterProxy.h b/Source/Internal/IGListAdapterProxy.h new file mode 100644 index 000000000..b1af43be6 --- /dev/null +++ b/Source/Internal/IGListAdapterProxy.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@class IGListAdapter; + +NS_ASSUME_NONNULL_BEGIN + +/** + A proxy that sends a custom set of selectors to an IGListAdapter object and the rest to a UICollectionViewDelegate + target. + */ +IGLK_SUBCLASSING_RESTRICTED +@interface IGListAdapterProxy : NSProxy + +/** + Create a new proxy object with targets and interceptor. + + @param collectionViewTarget A UICollectionViewDelegate conforming object that receives unintercepted messages. + @param scrollViewTarget A UIScrollViewDelegate conforming object that receives unintercepted messages. + @param interceptor An IGListAdapter object that intercepts a set of messages. + + @return A new IGListAdapterProxy object. + */ +- (instancetype)initWithCollectionViewTarget:(nullable id)collectionViewTarget + scrollViewTarget:(nullable id)scrollViewTarget + interceptor:(IGListAdapter *)interceptor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListAdapterProxy.m b/Source/Internal/IGListAdapterProxy.m new file mode 100644 index 000000000..6d7297e32 --- /dev/null +++ b/Source/Internal/IGListAdapterProxy.m @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListAdapterProxy.h" + +#import + +/** + Define messages that you want the IGListAdapter object to intercept. Pattern copied from + https://github.com/facebook/AsyncDisplayKit/blob/7b112a2dcd0391ddf3671f9dcb63521f554b78bd/AsyncDisplayKit/ASCollectionView.mm#L34-L53 + */ +static BOOL isInterceptedSelector(SEL sel) { + return ( + // UICollectionViewDelegate + sel == @selector(collectionView:didSelectItemAtIndexPath:) || + sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) || + sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) || + // UICollectionViewDelegateFlowLayout + sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) || + sel == @selector(collectionView:layout:insetForSectionAtIndex:) || + sel == @selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:) || + sel == @selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:) || + sel == @selector(collectionView:layout:referenceSizeForFooterInSection:) || + sel == @selector(collectionView:layout:referenceSizeForHeaderInSection:) || + // UIScrollViewDelegate + sel == @selector(scrollViewDidScroll:) || + sel == @selector(scrollViewWillBeginDragging:) || + sel == @selector(scrollViewDidEndDragging:willDecelerate:) + ); +} + +@interface IGListAdapterProxy () { + __weak id _collectionViewTarget; + __weak id _scrollViewTarget; + __weak IGListAdapter *_interceptor; +} + +@end + +@implementation IGListAdapterProxy + +- (instancetype)initWithCollectionViewTarget:(nullable id)collectionViewTarget + scrollViewTarget:(nullable id)scrollViewTarget + interceptor:(IGListAdapter *)interceptor { + IGParameterAssert(interceptor != nil); + // -[NSProxy init] is undefined + if (self) { + _collectionViewTarget = collectionViewTarget; + _scrollViewTarget = scrollViewTarget; + _interceptor = interceptor; + } + return self; +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + return isInterceptedSelector(aSelector) + || [_collectionViewTarget respondsToSelector:aSelector] + || [_scrollViewTarget respondsToSelector:aSelector]; +} + +- (id)forwardingTargetForSelector:(SEL)aSelector { + if (isInterceptedSelector(aSelector)) { + return _interceptor; + } + + // since UICollectionViewDelegate is a superset of UIScrollViewDelegate, first check if the method exists in + // _scrollViewTarget, otherwise use the _collectionViewTarget + return [_scrollViewTarget respondsToSelector:aSelector] ? _scrollViewTarget : _collectionViewTarget; +} + +// handling unimplemented methods and nil target/interceptor +// https://github.com/Flipboard/FLAnimatedImage/blob/76a31aefc645cc09463a62d42c02954a30434d7d/FLAnimatedImage/FLAnimatedImage.m#L786-L807 +- (void)forwardInvocation:(NSInvocation *)invocation { + void *nullPointer = NULL; + [invocation setReturnValue:&nullPointer]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { + return [NSObject instanceMethodSignatureForSelector:@selector(init)]; +} + +@end diff --git a/Source/Internal/IGListAdapterUpdaterInternal.h b/Source/Internal/IGListAdapterUpdaterInternal.h new file mode 100644 index 000000000..3ce7ddf2a --- /dev/null +++ b/Source/Internal/IGListAdapterUpdaterInternal.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import "IGListAdapterUpdater.h" + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXTERN void convertReloadToDeleteInsert(NSMutableIndexSet *reloads, + NSMutableIndexSet *deletes, + NSMutableIndexSet *inserts, + IGListIndexSetResult *result, + NSArray> *fromObjects); + +@interface IGListAdapterUpdater () + +@property (nonatomic, strong, readonly) NSMutableArray *completionBlocks; + +@property (nonatomic, copy, nullable) NSArray *fromObjects; +@property (nonatomic, copy, nullable) NSArray *toObjects; +@property (nonatomic, copy, nullable) NSArray *pendingTransitionToObjects; + +@property (nonatomic, assign) BOOL queuedUpdateIsAnimated; + +@property (nonatomic, strong, readonly) NSMutableSet *deleteIndexPaths; +@property (nonatomic, strong, readonly) NSMutableSet *insertIndexPaths; +@property (nonatomic, strong, readonly) NSMutableSet *reloadIndexPaths; +@property (nonatomic, strong, readonly) NSMutableIndexSet *reloadSections; + +@property (nonatomic, copy, nullable) IGListObjectTransitionBlock objectTransitionBlock; +@property (nonatomic, copy, nullable) NSMutableArray *itemUpdateBlocks; + +@property (nonatomic, copy, nullable) IGListReloadUpdateBlock reloadUpdates; +@property (nonatomic, assign, getter=hasQueuedReloadData) BOOL queuedReloadData; + +@property (nonatomic, assign) BOOL batchUpdateOrReloadInProgress; + +- (void)performReloadDataWithCollectionView:(UICollectionView *)collectionView; +- (void)performBatchUpdatesWithCollectionView:(UICollectionView *)collectionView; +- (void)cleanupState; +- (BOOL)hasChanges; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListDisplayHandler.h b/Source/Internal/IGListDisplayHandler.h new file mode 100644 index 000000000..c27644c9b --- /dev/null +++ b/Source/Internal/IGListDisplayHandler.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@class IGListAdapter; +@class IGListSectionController; + +@protocol IGListSectionType; + +NS_ASSUME_NONNULL_BEGIN + +IGLK_SUBCLASSING_RESTRICTED +@interface IGListDisplayHandler : NSObject + +/** + Tells the handler that a cell will be displayed in the IGListKit infra. + + @param cell A cell that will display. + @param listAdapter The adapter managing the infra. + @param sectionController The section controller the cell is in. + @param object The object associated with the section controller. + @param indexPath The index path of the cell in the UICollectionView. + */ +- (void)willDisplayCell:(UICollectionViewCell *)cell + forListAdapter:(IGListAdapter *)listAdapter + sectionController:(IGListSectionController *)sectionController + object:(id)object + indexPath:(NSIndexPath *)indexPath; + +/** + Tells the handler that a cell did end display in the IGListKit infra. + + @param cell A cell that did end display. + @param listAdapter The adapter managing the infra. + @param sectionController The section controller the cell is in. + @param indexPath The index path of the cell in the UICollectionView. + */ +- (void)didEndDisplayingCell:(UICollectionViewCell *)cell + forListAdapter:(IGListAdapter *)listAdapter + sectionController:(IGListSectionController *)sectionController + indexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListDisplayHandler.m b/Source/Internal/IGListDisplayHandler.m new file mode 100644 index 000000000..6d106b618 --- /dev/null +++ b/Source/Internal/IGListDisplayHandler.m @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListDisplayHandler.h" + +#import +#import +#import +#import + +@interface IGListDisplayHandler () + +@property (nonatomic, strong) NSCountedSet *visibleListSections; +@property (nonatomic, strong) NSMapTable *visibleCellObjectMap; + +@end + +@implementation IGListDisplayHandler + +- (instancetype)init { + if (self = [super init]) { + _visibleListSections = [[NSCountedSet alloc] init]; + _visibleCellObjectMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:0]; + } + return self; +} + +- (void)willDisplayCell:(UICollectionViewCell *)cell + forListAdapter:(IGListAdapter *)listAdapter + sectionController:(IGListSectionController *)sectionController + object:(id)object + indexPath:(NSIndexPath *)indexPath { + IGParameterAssert(cell != nil); + IGParameterAssert(listAdapter != nil); + IGParameterAssert(object != nil); + IGParameterAssert(indexPath != nil); + + id displayDelegate = [sectionController displayDelegate]; + + [displayDelegate listAdapter:listAdapter willDisplaySectionController:sectionController cell:cell atIndex:indexPath.item]; + + [self.visibleCellObjectMap setObject:object forKey:cell]; + + if ([self.visibleListSections countForObject:sectionController] == 0) { + [displayDelegate listAdapter:listAdapter willDisplaySectionController:sectionController]; + [listAdapter.delegate listAdapter:listAdapter willDisplayObject:object atIndex:indexPath.section]; + } + [self.visibleListSections addObject:sectionController]; +} + +- (void)didEndDisplayingCell:(UICollectionViewCell *)cell + forListAdapter:(IGListAdapter *)listAdapter + sectionController:(IGListSectionController *)sectionController + indexPath:(NSIndexPath *)indexPath { + IGParameterAssert(cell != nil); + IGParameterAssert(listAdapter != nil); + IGParameterAssert(indexPath != nil); + + const NSUInteger section = indexPath.section; + + NSMapTable *cellObjectMap = self.visibleCellObjectMap; + id object = [cellObjectMap objectForKey:cell]; + [cellObjectMap removeObjectForKey:cell]; + + if (object == nil || sectionController == nil) { + return; + } + + id displayDelegate = [sectionController displayDelegate]; + [displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:sectionController cell:cell atIndex:indexPath.item]; + + NSCountedSet *visibleSections = self.visibleListSections; + [visibleSections removeObject:sectionController]; + if ([visibleSections countForObject:sectionController] == 0) { + [displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:sectionController]; + [listAdapter.delegate listAdapter:listAdapter didEndDisplayingObject:object atIndex:section]; + } +} + +@end diff --git a/Source/Internal/IGListIndexPathResultInternal.h b/Source/Internal/IGListIndexPathResultInternal.h new file mode 100644 index 000000000..f60b56493 --- /dev/null +++ b/Source/Internal/IGListIndexPathResultInternal.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface IGListIndexPathResult() + +- (instancetype)initWithInserts:(NSArray *)inserts + deletes:(NSArray *)deletes + updates:(NSArray *)updates + moves:(NSArray *)moves + oldIndexPathMap:(NSMapTable, NSIndexPath *> *)oldIndexPathMap + newIndexPathMap:(NSMapTable, NSIndexPath *> *)newIndexPathMap; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListIndexSetResultInternal.h b/Source/Internal/IGListIndexSetResultInternal.h new file mode 100644 index 000000000..c66284e44 --- /dev/null +++ b/Source/Internal/IGListIndexSetResultInternal.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface IGListIndexSetResult() + +- (instancetype)initWithInserts:(NSIndexSet *)inserts + deletes:(NSIndexSet *)deletes + updates:(NSIndexSet *)updates + moves:(NSArray *)moves + oldIndexMap:(NSMapTable, NSNumber *> *)oldIndexMap + newIndexMap:(NSMapTable, NSNumber *> *)newIndexMap; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListMoveIndexInternal.h b/Source/Internal/IGListMoveIndexInternal.h new file mode 100644 index 000000000..0ab57dea1 --- /dev/null +++ b/Source/Internal/IGListMoveIndexInternal.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface IGListMoveIndex () + +- (instancetype)initWithFrom:(NSUInteger)from to:(NSUInteger)to NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListMoveIndexPathInternal.h b/Source/Internal/IGListMoveIndexPathInternal.h new file mode 100644 index 000000000..c53fb19e1 --- /dev/null +++ b/Source/Internal/IGListMoveIndexPathInternal.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface IGListMoveIndexPath () + +- (instancetype)initWithFrom:(NSIndexPath *)from to:(NSIndexPath *)to NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListSectionControllerInternal.h b/Source/Internal/IGListSectionControllerInternal.h new file mode 100644 index 000000000..f7b92d90b --- /dev/null +++ b/Source/Internal/IGListSectionControllerInternal.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListSectionController.h" + +FOUNDATION_EXTERN void IGListSectionControllerPushThread(UIViewController *viewController, id collectionContext); + +FOUNDATION_EXTERN void IGListSectionControllerPopThread(void); + +@interface IGListSectionController() + +@property (nonatomic, weak, readwrite) id collectionContext; + +@property (nonatomic, weak, readwrite) UIViewController *viewController; + +@end diff --git a/Source/Internal/IGListSectionMap.h b/Source/Internal/IGListSectionMap.h new file mode 100644 index 000000000..92efb5a93 --- /dev/null +++ b/Source/Internal/IGListSectionMap.h @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@class IGListSectionController; +@protocol IGListSectionType; + +NS_ASSUME_NONNULL_BEGIN + +/** + The IGListSectionMap provides a way to map a collection of objects to a collection of section controllers and achieve + constant-time lookups O(1). + + IGListSectionMap is a mutable object and does not garauntee thread safety. + */ +IGLK_SUBCLASSING_RESTRICTED +@interface IGListSectionMap : NSObject + +- (instancetype)initWithMapTable:(NSMapTable *)mapTable NS_DESIGNATED_INITIALIZER; + +/** + The objects stored in the map. + */ +@property (nonatomic, strong, readonly) NSArray *objects; + +/** + Update the map with objects and the section controller counterparts. + + @param objects The objects in the collection. + @param sectionControllers The section controllers that map to each object. + */ +- (void)updateWithObjects:(NSArray > *)objects sectionControllers:(NSArray > *)sectionControllers; + +/** + Fetch a section controller given a section. + + @param section The section index of the section controller. + + @return A section controller. + */ +- (nullable IGListSectionController *)sectionControllerForSection:(NSUInteger)section; + +/** + Fetch the object for a section + + @param section The section index of the object. + + @return The object corresponding to the section. + */ +- (id)objectForSection:(NSUInteger)section; + +/** + Fetch a section controller given an object. Can return nil. + + @param object The object that maps to a section controller. + + @return A section controller. + */ +- (nullable id)sectionControllerForObject:(id)object; + +/** + Look up the section index for a section controller. + + @param sectionController The list to look up. + + @return The section index of the given section controller if it exists, NSNotFound otherwise. + */ +- (NSUInteger)sectionForSectionController:(id)sectionController; + +/** + Look up the section index for an object. + + @param object The object to look up. + + @return The section index of the given object if it exists, NSNotFound otherwise. + */ +- (NSUInteger)sectionForObject:(id)object; + +/** + Remove all saved objects and section controllers. + */ +- (void)reset; + +/** + Update an object with a new instance. + */ +- (void)updateObject:(id)object; + +/** + Applies a given block object to the entries of the section controller map. + + @param block A block object to operate on entries in the section controller map. + */ +- (void)enumerateUsingBlock:(void (^)(id object, IGListSectionController *sectionController, NSUInteger section, BOOL *stop))block; + +- (id)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Internal/IGListSectionMap.m b/Source/Internal/IGListSectionMap.m new file mode 100644 index 000000000..323cde0cf --- /dev/null +++ b/Source/Internal/IGListSectionMap.m @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListSectionMap.h" + +#import + +@interface IGListSectionMap () + +// both of these maps allow fast lookups of objects, list objects, and indexes +@property (nonatomic, strong, readonly) NSMapTable *, id> *sectionControllerToObjectMap; +@property (nonatomic, strong, readonly) NSMapTable *, NSNumber *> *sectionControllerToSectionMap; + +@property (nonatomic, strong, readwrite) NSArray *objects; + +@end + +@implementation IGListSectionMap + +- (instancetype)initWithMapTable:(NSMapTable *)mapTable { + IGParameterAssert(mapTable != nil); + + if (self = [super init]) { + _sectionControllerToObjectMap = [mapTable copy]; + + // lookup list objects by pointer equality + _sectionControllerToSectionMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory | NSMapTableObjectPointerPersonality + valueOptions:NSMapTableStrongMemory + capacity:0]; + _objects = [NSArray new]; + } + return self; +} + + +#pragma mark - Public API + +- (NSUInteger)sectionForSectionController:(IGListSectionController *)sectionController { + IGParameterAssert(sectionController != nil); + + NSNumber *index = [self.sectionControllerToSectionMap objectForKey:sectionController]; + return index != nil ? [index unsignedIntegerValue] : NSNotFound; +} + +- (IGListSectionController *)sectionControllerForSection:(NSUInteger)section { + return [self.sectionControllerToObjectMap objectForKey:[self objectForSection:section]]; +} + +- (void)updateWithObjects:(NSArray *)objects sectionControllers:(NSArray *)sectionControllers { + IGParameterAssert(objects.count == sectionControllers.count); + + self.objects = [objects copy]; + + [self reset]; + + [objects enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) { + IGListSectionController *sectionController = sectionControllers[idx]; + + // set the index of the list for easy reverse lookup + [self.sectionControllerToSectionMap setObject:@(idx) forKey:sectionController]; + [self.sectionControllerToObjectMap setObject:sectionController forKey:object]; + }]; +} + +- (nullable IGListSectionController *)sectionControllerForObject:(id)object { + IGParameterAssert(object != nil); + + return [self.sectionControllerToObjectMap objectForKey:object]; +} + +- (id)objectForSection:(NSUInteger)section { + return self.objects[section]; +} + +- (NSUInteger)sectionForObject:(id)object { + IGParameterAssert(object != nil); + + id sectionController = [self sectionControllerForObject:object]; + if (sectionController == nil) { + return NSNotFound; + } else { + return [self sectionForSectionController:sectionController]; + } +} + +- (void)reset { + [self.sectionControllerToSectionMap removeAllObjects]; + [self.sectionControllerToObjectMap removeAllObjects]; +} + +- (void)updateObject:(id)object { + IGParameterAssert(object != nil); + const NSUInteger section = [self sectionForObject:object]; + id sectionController = [self sectionControllerForObject:object]; + [self.sectionControllerToSectionMap setObject:@(section) forKey:sectionController]; + [self.sectionControllerToObjectMap setObject:sectionController forKey:object]; + + NSMutableArray *mobjects = [self.objects mutableCopy]; + mobjects[section] = object; + self.objects = [mobjects copy]; +} + +- (void)enumerateUsingBlock:(void (^)(id object, IGListSectionController *sectionController, NSUInteger section, BOOL *stop))block { + IGParameterAssert(block != nil); + + BOOL stop = NO; + NSArray *objects = self.objects; + for (NSUInteger section = 0; section < objects.count; section++) { + id object = objects[section]; + IGListSectionController *sectionController = [self sectionControllerForObject:object]; + block(object, sectionController, section, &stop); + if (stop) { + break; + } + } +} + + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + IGListSectionMap *copy = [[IGListSectionMap allocWithZone:zone] initWithMapTable:self.sectionControllerToObjectMap]; + copy->_sectionControllerToSectionMap = [self.sectionControllerToSectionMap copy]; + copy->_objects = [self.objects copy]; + return copy; +} + +@end diff --git a/Source/Internal/IGListStackedSectionControllerInternal.h b/Source/Internal/IGListStackedSectionControllerInternal.h new file mode 100644 index 000000000..6fb8b7246 --- /dev/null +++ b/Source/Internal/IGListStackedSectionControllerInternal.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGListStackedSectionController.h" + +@interface IGListStackedSectionController () +< +IGListCollectionContext, +IGListDisplayDelegate, +IGListScrollDelegate +> + +@property (nonatomic, strong, readonly) NSOrderedSet<__kindof IGListSectionController *> *sectionControllers; + +/// An array the length of the total number of items in the stack, pointing to an section controller for the item index. +@property (nonatomic, copy) NSArray *> *sectionControllersForItems; + +/// An array of index offsets for each item in the flattened stack. +@property (nonatomic, copy) NSArray *sectionControllerOffsets; + +/// A cached collection of the number of items summed from each section controller in the stack. +@property (nonatomic, assign) NSUInteger flattenedNumberOfItems; + +/// A counted set of the visible section controllers, used to forward granular display events to child section controllers +@property (nonatomic, strong, readonly) NSCountedSet *visibleSectionControllers; + +- (IGListSectionController *)sectionControllerForObjectIndex:(NSUInteger)itemIndex; +- (NSUInteger)offsetForSectionController:(IGListSectionController *)sectionController; + +@end diff --git a/Source/Internal/IGListWorkingRangeHandler.h b/Source/Internal/IGListWorkingRangeHandler.h new file mode 100644 index 000000000..ba0251987 --- /dev/null +++ b/Source/Internal/IGListWorkingRangeHandler.h @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class IGListAdapter; + +@protocol IGListSectionType; + +@interface IGListWorkingRangeHandler : NSObject + +/** + Initializes the working range handler. + + @param workingRangeSize the number of sections beyond the visible viewport that should be considered within the working + range. Applies equally in both directions above and below the viewport. + */ +- (instancetype)initWithWorkingRangeSize:(NSInteger)workingRangeSize; + +/** + Tells the handler that a cell will be displayed in the IGListKit infra. + + @param indexPath The index path of the cell in the UICollectionView. + @param listAdapter The adapter managing the infra. + */ +- (void)willDisplayItemAtIndexPath:(NSIndexPath *)indexPath + forListAdapter:(IGListAdapter *)listAdapter; + +/** + Tells the handler that a cell did end display in the IGListKit infra. + + @param indexPath The index path of the cell in the UICollectionView. + @param listAdapter The adapter managing the infra. + */ +- (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath + forListAdapter:(IGListAdapter *)listAdapter; + +@end diff --git a/Source/Internal/IGListWorkingRangeHandler.mm b/Source/Internal/IGListWorkingRangeHandler.mm new file mode 100644 index 000000000..31257f88b --- /dev/null +++ b/Source/Internal/IGListWorkingRangeHandler.mm @@ -0,0 +1,160 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListWorkingRangeHandler.h" + +#import +#import +#import + +#import +#import +#import +#import + +#import "IGListWorkingRangeDelegate.h" + +struct _IGListWorkingRangeHandlerIndexPath { + NSInteger section; + NSInteger row; + size_t hash; + + bool operator==(const _IGListWorkingRangeHandlerIndexPath &other) const { + return (section == other.section && row == other.row); + } +}; + +struct _IGListWorkingRangeHandlerSectionControllerWrapper { + IGListSectionController *sectionController; + + bool operator==(const _IGListWorkingRangeHandlerSectionControllerWrapper &other) const { + return (sectionController == other.sectionController); + } +}; + +struct _IGListWorkingRangeHandlerIndexPathHash { + size_t operator()(const _IGListWorkingRangeHandlerIndexPath& o) const { + return (size_t)o.hash; + } +}; + +struct _IGListWorkingRangeHashID { + size_t operator()(const _IGListWorkingRangeHandlerSectionControllerWrapper &o) const { + return (size_t)[o.sectionController hash]; + } +}; + +typedef std::unordered_set<_IGListWorkingRangeHandlerSectionControllerWrapper, _IGListWorkingRangeHashID> _IGListWorkingRangeSectionControllerSet; +typedef std::unordered_set<_IGListWorkingRangeHandlerIndexPath, _IGListWorkingRangeHandlerIndexPathHash> _IGListWorkingRangeIndexPathSet; + +@interface IGListWorkingRangeHandler () + +@property (nonatomic, assign, readonly) NSInteger workingRangeSize; + +@end + +@implementation IGListWorkingRangeHandler { + _IGListWorkingRangeIndexPathSet _visibleSectionIndices; + _IGListWorkingRangeSectionControllerSet _workingRangeSectionControllers; +} + +- (instancetype)initWithWorkingRangeSize:(NSInteger)workingRangeSize { + if (self = [super init]) { + _workingRangeSize = workingRangeSize; + } + return self; +} + +- (void)willDisplayItemAtIndexPath:(NSIndexPath *)indexPath + forListAdapter:(IGListAdapter *)listAdapter { + IGParameterAssert(indexPath != nil); + IGParameterAssert(listAdapter != nil); + + _visibleSectionIndices.insert({ + .section = indexPath.section, + .row = indexPath.row, + .hash = indexPath.hash + }); + + [self updateWorkingRangesWithListAdapter:listAdapter]; +} + +- (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath + forListAdapter:(IGListAdapter *)listAdapter { + IGParameterAssert(indexPath != nil); + IGParameterAssert(listAdapter != nil); + + _visibleSectionIndices.erase({ + .section = indexPath.section, + .row = indexPath.row, + .hash = indexPath.hash + }); + + [self updateWorkingRangesWithListAdapter:listAdapter]; +} + +#pragma mark - Working Ranges + +- (void)updateWorkingRangesWithListAdapter:(IGListAdapter *)listAdapter { + IGAssertMainThread(); + // This method is optimized C++ to improve straight-line speed of these operations. Change at your peril. + + // We use a std::set because it is ordered. + std::set visibleSectionSet = std::set(); + for (const _IGListWorkingRangeHandlerIndexPath &indexPath : _visibleSectionIndices) { + visibleSectionSet.insert(indexPath.section); + } + + NSInteger start; + NSInteger end; + if (visibleSectionSet.size() == 0) { + // We're now devoid of any visible sections. Bail + start = 0; + end = 0; + } else { + start = MAX(*visibleSectionSet.begin() - _workingRangeSize, 0); + end = MIN(*visibleSectionSet.rbegin() + 1 + _workingRangeSize, (NSInteger)listAdapter.objects.count); + } + + // Build the current set of working range section controllers + _IGListWorkingRangeSectionControllerSet workingRangeSectionControllers (visibleSectionSet.size()); + for (NSInteger idx = start; idx < end; idx++) { + id item = [listAdapter objectAtSection:idx]; + id sectionController = [listAdapter sectionControllerForObject:item]; + workingRangeSectionControllers.insert({sectionController}); + } + + IGAssert(workingRangeSectionControllers.size() < 1000, @"This algorithm is way too slow with so many objects:%lu", workingRangeSectionControllers.size()); + + // Tell any new section controllers that they have entered the working range + for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : workingRangeSectionControllers) { + // Check if the item exists in the old working range item array. + auto it = _workingRangeSectionControllers.find(wrapper); + if (it == _workingRangeSectionControllers.end()) { + // The section controller isn't in the existing list, so it's new. + id workingRangeDelegate = wrapper.sectionController.workingRangeDelegate; + [workingRangeDelegate listAdapter:listAdapter sectionControllerWillEnterWorkingRange:wrapper.sectionController]; + } + } + + // Tell any removed section controllers that they have exited the working range + for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : _workingRangeSectionControllers) { + // Check if the item exists in the new list of section controllers + auto it = workingRangeSectionControllers.find(wrapper); + if (it == workingRangeSectionControllers.end()) { + // If the item does not exist in the new list, then it's been removed. + id workingRangeDelegate = wrapper.sectionController.workingRangeDelegate; + [workingRangeDelegate listAdapter:listAdapter sectionControllerDidExitWorkingRange:wrapper.sectionController]; + } + } + + _workingRangeSectionControllers = workingRangeSectionControllers; +} + +@end diff --git a/Source/Internal/NSIndexSet+PrettyDescription.h b/Source/Internal/NSIndexSet+PrettyDescription.h new file mode 100644 index 000000000..5c9443277 --- /dev/null +++ b/Source/Internal/NSIndexSet+PrettyDescription.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface NSIndexSet (PrettyDescription) + +- (NSString *)prettyDescription; + +@end diff --git a/Source/Internal/NSIndexSet+PrettyDescription.m b/Source/Internal/NSIndexSet+PrettyDescription.m new file mode 100644 index 000000000..cdf9e6e85 --- /dev/null +++ b/Source/Internal/NSIndexSet+PrettyDescription.m @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "NSIndexSet+PrettyDescription.h" + +@implementation NSIndexSet (PrettyDescription) + +- (NSString *)prettyDescription { + NSMutableArray *indexes = [[NSMutableArray alloc] init]; + [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + [indexes addObject:@(idx)]; + }]; + return [indexes componentsJoinedByString:@", "]; +} + +@end diff --git a/Source/Internal/UICollectionView+IGListBatchUpdateData.h b/Source/Internal/UICollectionView+IGListBatchUpdateData.h new file mode 100644 index 000000000..36af9bfc5 --- /dev/null +++ b/Source/Internal/UICollectionView+IGListBatchUpdateData.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class IGListBatchUpdateData; + +@interface UICollectionView (IGListBatchUpdateData) + +- (void)ig_applyBatchUpdateData:(IGListBatchUpdateData *)updateData; + +@end diff --git a/Source/Internal/UICollectionView+IGListBatchUpdateData.m b/Source/Internal/UICollectionView+IGListBatchUpdateData.m new file mode 100644 index 000000000..2c91b53a8 --- /dev/null +++ b/Source/Internal/UICollectionView+IGListBatchUpdateData.m @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "UICollectionView+IGListBatchUpdateData.h" + +#import "IGListBatchUpdateData.h" + +@implementation UICollectionView (IGListBatchUpdateData) + +- (void)ig_applyBatchUpdateData:(IGListBatchUpdateData *)updateData { + [self deleteItemsAtIndexPaths:[updateData.deleteIndexPaths allObjects]]; + [self insertItemsAtIndexPaths:[updateData.insertIndexPaths allObjects]]; + [self reloadItemsAtIndexPaths:[updateData.reloadIndexPaths allObjects]]; + + for (IGListMoveIndex *move in updateData.moveSections) { + [self moveSection:move.from toSection:move.to]; + } + + [self deleteSections:updateData.deleteSections]; + [self insertSections:updateData.insertSections]; +} + +@end diff --git a/Source/NSObject+IGListDiffable.h b/Source/NSObject+IGListDiffable.h new file mode 100644 index 000000000..f5c7a654d --- /dev/null +++ b/Source/NSObject+IGListDiffable.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +/** + This category adds diffing comparisons similar to adding the object into an NSSet, where the object's isEqual: method + drives the uniqueness of the object. + + For instance, an NSString's isEqual: will compare the value of the strings. So if you were to diff @"cat" and @"cat" + each object would have the same diff identifier. + + However objects that don't implement a custom isEqual: (e.g. the NSObject base class), the diff will default to simple + pointer comparisons to establish uniqueness. + */ +@interface NSObject (IGListDiffable) + +@end diff --git a/Source/NSObject+IGListDiffable.m b/Source/NSObject+IGListDiffable.m new file mode 100644 index 000000000..6bb18bdef --- /dev/null +++ b/Source/NSObject+IGListDiffable.m @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "NSObject+IGListDiffable.h" + +@implementation NSObject (IGListDiffable) + +- (id)diffIdentifier { + return self; +} + +@end diff --git a/Tests/IGListAdapterE2ETests.m b/Tests/IGListAdapterE2ETests.m new file mode 100644 index 000000000..4f2cb01c3 --- /dev/null +++ b/Tests/IGListAdapterE2ETests.m @@ -0,0 +1,1025 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import + +#import "IGListAdapterInternal.h" +#import "IGListTestOffsettingLayout.h" +#import "IGTestCell.h" +#import "IGTestDelegateController.h" +#import "IGTestDelegateDataSource.h" +#import "IGTestObject.h" + +#define genIndexPath(s) [NSIndexPath indexPathForItem:0 inSection:s] +#define genTestObject(k, v) [[IGTestObject alloc] initWithKey:k value:v] + +#define genExpectation [self expectationWithDescription:NSStringFromSelector(_cmd)] + +@interface IGListAdapterE2ETests : XCTestCase + +@property (nonatomic, strong) IGListCollectionView *collectionView; +@property (nonatomic, strong) IGListAdapter *adapter; +@property (nonatomic, strong) IGListAdapterUpdater *updater; +@property (nonatomic, strong) IGTestDelegateDataSource *dataSource; +@property (nonatomic, strong) UIWindow *window; + +@end + +@implementation IGListAdapterE2ETests + +- (void)setUp { + [super setUp]; + + self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + self.collectionView = [[IGListCollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layout]; + + [self.window addSubview:self.collectionView]; + + self.dataSource = [[IGTestDelegateDataSource alloc] init]; + + self.updater = [[IGListAdapterUpdater alloc] init]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:self.updater viewController:nil workingRangeSize:2]; +} + +- (void)tearDown { + [super tearDown]; + + self.window = nil; + self.collectionView = nil; + self.adapter = nil; + self.dataSource = nil; +} + +- (void)setupWithObjects:(NSArray *)objects { + self.dataSource.objects = objects; + self.adapter.collectionView = self.collectionView; + self.adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; +} + +- (void)test_whenSettingUpTest_thenCollectionViewIsLoaded { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @3) + ]]; + XCTAssertEqual(self.collectionView.numberOfSections, 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 3); +} + +- (void)test_whenUsingStringValue_thenCellLabelsAreConfigured { + [self setupWithObjects:@[ + genTestObject(@0, @"Foo"), + genTestObject(@1, @"Bar") + ]]; + + IGTestCell *cell = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(0)]; + XCTAssertEqualObjects(cell.label.text, @"Foo"); + XCTAssertEqual(cell.delegate, [self.adapter sectionControllerForObject:self.dataSource.objects[0]]); +} + +- (void)test_whenUpdating_withEqualObjects_thatCellConfigurationDoesntChange { + [self setupWithObjects:@[ + genTestObject(@0, @"Foo"), + genTestObject(@1, @"Bar") + ]]; + + // Get the section controller before we change the data source or perform updates + id c0 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + + // Set equal but new-instance objects on the data source + self.dataSource.objects = @[ + genTestObject(@0, @"Foo"), + genTestObject(@1, @"Bar") + ]; + + // Perform updates on the adapter and check that the cell config uses the same section controller as before the updates + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + IGTestCell *cell = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(0)]; + XCTAssertEqualObjects(cell.label.text, @"Foo"); + XCTAssertNotNil(cell.delegate); + XCTAssertEqual(cell.delegate, c0); + XCTAssertEqual(cell.delegate, [self.adapter sectionControllerForObject:self.dataSource.objects[0]]); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenReloadingItem_cellConfigurationChanges { + [self setupWithObjects:@[ + genTestObject(@0, @"Foo"), + genTestObject(@1, @"Bar") + ]]; + + // make sure our cells are propertly configured + IGTestCell *cell1 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(0)]; + IGTestCell *cell2 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(1)]; + XCTAssertEqualObjects(cell1.label.text, @"Foo"); + XCTAssertEqualObjects(cell2.label.text, @"Bar"); + + // Change the string value of both instances in the data source + IGTestObject *item1 = self.dataSource.objects[0]; + item1.value = @"Baz"; + IGTestObject *item2 = self.dataSource.objects[1]; + item2.value = @"Quz"; + + // Only reload the first item, not the second + [self.adapter reloadObjects:@[item1]]; + + // The collection view will likely create new cells + cell1 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(0)]; + cell2 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(1)]; + + // Make sure that the cell in the first section was reloaded + XCTAssertEqualObjects(cell1.label.text, @"Baz"); + // The cell in the second section should not be reloaded and should equal the string value from setup + XCTAssertEqualObjects(cell2.label.text, @"Bar"); +} + +- (void)test_whenObjectEqualityChanges_thatSectionCountChanges { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2) + ]]; + + self.dataSource.objects = @[ + genTestObject(@1, @2), + genTestObject(@2, @3), // updated to 3 items (from 2) + genTestObject(@3, @2), // insert new object + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + XCTAssertEqual(self.collectionView.numberOfSections, 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 2); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenUpdatesComplete_thatCellsExist { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2), + ]]; + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]); + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]); + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]); + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:1]]); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenReloadDataCompletes_thatCellsExist { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2) + ]]; + XCTestExpectation *expectation = genExpectation; + [self.adapter reloadDataWithCompletion:^(BOOL finished) { + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]); + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]); + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]); + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:1]]); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenSectionControllerInsertsIndexes_thatCountsAreUpdated { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2) + ]]; + + IGTestObject *object = self.dataSource.objects[0]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object]; + + XCTestExpectation *expectation = genExpectation; + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @3; + [sectionController.collectionContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:2]]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenSectionControllerDeletesIndexes_thatCountsAreUpdated { + // 2 sections each with 2 objects + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2) + ]]; + + IGTestObject *object = self.dataSource.objects[0]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object]; + + XCTestExpectation *expectation = genExpectation; + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @1; + [sectionController.collectionContext deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenSectionControllerReloadsIndexes_thatCellConfigurationUpdates { + [self setupWithObjects:@[ + genTestObject(@1, @"a"), + genTestObject(@2, @"b") + ]]; + XCTAssertEqual([self.collectionView numberOfSections], 2); + IGTestCell *cell = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + XCTAssertEqualObjects(cell.label.text, @"a"); + + IGTestObject *object = self.dataSource.objects[0]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object]; + + XCTestExpectation *expectation = genExpectation; + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @"c"; + [sectionController.collectionContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + IGTestCell *updatedCell = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + XCTAssertEqualObjects(updatedCell.label.text, @"c"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenSectionControllerReloads_thatCountsAreUpdated { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2) + ]]; + + IGTestObject *object = self.dataSource.objects[0]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object]; + + XCTestExpectation *expectation = genExpectation; + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @3; + [sectionController.collectionContext reloadSectionController:sectionController]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenPerformingUpdates_withSectionControllerMutations_thatCollectionCountsAreUpdated { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2) + ]]; + IGTestObject *object1 = self.dataSource.objects[0]; + IGTestObject *object2 = self.dataSource.objects[1]; + + // insert a new object in front of the one we are doing an item-level insert on + self.dataSource.objects = @[ + genTestObject(@3, @1), // new + object1, + object2, + ]; + + IGListSectionController *sectionController1 = [self.adapter sectionControllerForObject:object1]; + IGListSectionController *sectionController2 = [self.adapter sectionControllerForObject:object2]; + + [self.adapter performUpdatesAnimated:YES completion:nil]; + + XCTestExpectation *expectation = genExpectation; + [sectionController1.collectionContext performBatchAnimated:YES updates:^{ + object1.value = @1; + object2.value = @3; + [sectionController1.collectionContext deleteInSectionController:sectionController1 atIndexes:[NSIndexSet indexSetWithIndex:0]]; + [sectionController2.collectionContext insertInSectionController:sectionController2 atIndexes:[NSIndexSet indexSetWithIndex:2]]; + [sectionController2.collectionContext reloadInSectionController:sectionController2 atIndexes:[NSIndexSet indexSetWithIndex:0]]; + } completion:^(BOOL finished2) { + // 3 sections now b/c of the insert + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenSectionControllerMoves_withSectionControllerMutations_thatCollectionViewWorks { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2) + ]]; + + IGTestObject *object = self.dataSource.objects[0]; + self.dataSource.objects = @[ + genTestObject(@2, @2), + object, // moved from 0 to 1 + ]; + + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object]; + + // queue the update that performs the section move + [self.adapter performUpdatesAnimated:YES completion:nil]; + + XCTestExpectation *expectation = genExpectation; + + // queue an item update that gets batched with the section move + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @3; + [sectionController.collectionContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:2]]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + // the object we are tracking should now be in section 1 and have 3 items + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenItemIsRemoved_withSectionControllerMutations_thatCollectionViewWorks { + // 2 sections each with 2 objects + [self setupWithObjects:@[ + genTestObject(@2, @2), + genTestObject(@1, @2) + ]]; + IGTestObject *object = self.dataSource.objects[1]; + + // object at index 1 deleted + self.dataSource.objects = @[ + genTestObject(@2, @2), + ]; + + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object]; + + [self.adapter performUpdatesAnimated:YES completion:nil]; + + XCTestExpectation *expectation = genExpectation; + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @1; + [sectionController.collectionContext deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 1); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenPerformingUpdates_withUnequalItem_withItemMoving_thatCollectionViewCountsUpdate { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2), + ]]; + + self.dataSource.objects = @[ + genTestObject(@3, @2), + genTestObject(@1, @3), // moved from index 0 to 1, value changed from 2 to 3 + genTestObject(@2, @2), + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenPerformingUpdates_withItemMoving_withSectionControllerReloadIndexes_thatCollectionViewCountsUpdate { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @3), + ]]; + + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + + self.dataSource.objects = @[ + genTestObject(@2, @3), + genTestObject(@1, @2), // moved from index 0 to 1 + ]; + + [self.adapter performUpdatesAnimated:YES completion:nil]; + + XCTestExpectation *expectation = genExpectation; + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + [sectionController.collectionContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenPerformingUpdates_withSectionControllerReloadIndexes_withItemDeleted_thatCollectionViewCountsUpdate { + [self setupWithObjects:@[ + genTestObject(@1, @2), // item that will be deleted + genTestObject(@2, @3), + ]]; + + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + + self.dataSource.objects = @[ + genTestObject(@2, @3), + ]; + + [self.adapter performUpdatesAnimated:YES completion:nil]; + + XCTestExpectation *expectation = genExpectation; + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + [sectionController.collectionContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenPerformingUpdates_withNewItemInstances_thatSectionControllersEqual { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2) + ]]; + + // grab section controllers before updating the objects + NSArray *beforeupdateObjects = self.dataSource.objects; + IGListSectionController *sectionController1 = [self.adapter sectionControllerForObject:beforeupdateObjects.firstObject]; + IGListSectionController *sectionController2 = [self.adapter sectionControllerForObject:beforeupdateObjects.lastObject]; + + self.dataSource.objects = @[ + genTestObject(@1, @3), // new instance, value changed from 2 to 3 + genTestObject(@2, @2), // new instance but unchanged + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); + + NSArray *afterupdateObjects = [self.adapter objects]; + // pointer equality + XCTAssertEqual([self.adapter sectionControllerForObject:afterupdateObjects.firstObject], sectionController1); + XCTAssertEqual([self.adapter sectionControllerForObject:afterupdateObjects.lastObject], sectionController2); + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenPerformingMultipleUpdates_withNewItemInstances_thatSectionControllersReceiveNewInstances { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2), + ]]; + + id object = self.dataSource.objects[0]; + IGTestDelegateController *sectionController = [self.adapter sectionControllerForObject:object]; + + // test delegate controller counts the number of times it receives -didUpdateToItem: + XCTAssertEqual(sectionController.updateCount, 1); + + self.dataSource.objects = @[ + object, // same object instance + genTestObject(@3, @2), + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + XCTAssertEqual(sectionController, [self.adapter sectionControllerForObject:[self.adapter objects][0]]); + + // should not have received -didUpdateToItem: since the instance did not change + XCTAssertEqual(sectionController.updateCount, 1); + + self.dataSource.objects = @[ + genTestObject(@1, @2), // new instance but equal + genTestObject(@3, @2), + ]; + + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished3) { + XCTAssertEqual(sectionController, [self.adapter sectionControllerForObject:[self.adapter objects][0]]); + + // a new instance was used, make sure the section controller was updated + XCTAssertEqual(sectionController.updateCount, 2); + + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenQueryingCollectionContext_withNewItemInstances_thatSectionMatchesCurrentIndex { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2), + ]]; + + IGTestDelegateController *sectionController = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + self.dataSource.objects = @[ + genTestObject(@2, @2), + genTestObject(@1, @2), // new instance but equal + genTestObject(@3, @2), + ]; + + __block BOOL executedUpdateBlock = NO; + __weak __typeof__(sectionController) weakSectionController = sectionController; + sectionController.itemUpdateBlock = ^{ + executedUpdateBlock = YES; + XCTAssertEqual([weakSectionController.collectionContext sectionForSectionController:weakSectionController], 1); + }; + + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished3) { + XCTAssertTrue(executedUpdateBlock); + + [expectation fulfill]; + }]; + }]; + + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenSectionControllerMutates_withReloadData_thatSectionControllerMutationIsApplied { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2), + ]]; + IGTestObject *object = self.dataSource.objects[0]; + IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object]; + + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @3; + [sectionController.collectionContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:2]]; + } completion:nil]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter reloadDataWithCompletion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + + // check that the count of items in section 0 was updated from the previous batch update block + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenContentOffsetChanges_withPerformUpdates_thatCollectionViewWorks { + // this test layout changes the offset in -prepareLayout which occurs somewhere between the update block being + // applied and the completion block + self.collectionView.collectionViewLayout = [IGListTestOffsettingLayout new]; + + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2), + genTestObject(@3, @2), + ]]; + + // remove the last object to check that we don't access OOB section controller when the layout changes the offset + self.dataSource.objects = @[ + genTestObject(@1, @2), + genTestObject(@2, @2), + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenReloadingItems_withNewItemInstances_thatSectionControllersReceiveNewInstances { + [self setupWithObjects:@[ + genTestObject(@1, @2), + genTestObject(@2, @2), + genTestObject(@3, @2), + ]]; + + IGTestDelegateController *sectionController1 = [self.adapter sectionControllerForObject:genTestObject(@1, @2)]; + IGTestDelegateController *sectionController2 = [self.adapter sectionControllerForObject:genTestObject(@2, @2)]; + + NSArray *newObjects = @[ + genTestObject(@1, @3), + genTestObject(@2, @3), + ]; + [self.adapter reloadObjects:newObjects]; + + XCTAssertEqual(sectionController1.item, newObjects[0]); + XCTAssertEqual(sectionController2.item, newObjects[1]); + XCTAssertTrue([[self.adapter.sectionMap objects] indexOfObjectIdenticalTo:newObjects[0]] != NSNotFound); + XCTAssertTrue([[self.adapter.sectionMap objects] indexOfObjectIdenticalTo:newObjects[1]] != NSNotFound); +} + +- (void)test_whenReloadingItems_withPerformUpdates_thatReloadIsApplied { + [self setupWithObjects:@[ + genTestObject(@1, @1), + genTestObject(@2, @2), + genTestObject(@3, @3), + ]]; + + IGTestObject *object = self.dataSource.objects[0]; + IGTestDelegateController *sectionController = [self.adapter sectionControllerForObject:object]; + + // using performBatchAnimated: to mimic re-entrant item reload + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @4; // from @1 + [self.adapter reloadObjects:@[object]]; + } completion:nil]; + + // object is moved from position 0 to 1 + // it is also mutated in the previous update block AND queued for a reload + self.dataSource.objects = @[ + genTestObject(@3, @3), + object, + genTestObject(@2, @2), + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 4); // reloaded section + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 2); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenSectionControllerMutates_whenThereIsNoWindow_thatCollectionViewCountsAreUpdated { + // remove the collection view from self.window so that we use reloadData + [self.collectionView removeFromSuperview]; + + [self setupWithObjects:@[ + genTestObject(@1, @8) + ]]; + IGTestObject *object = self.dataSource.objects[0]; + + IGTestDelegateController *sectionController = [self.adapter sectionControllerForObject:object]; + + XCTestExpectation *expectation = genExpectation; + // using performBatchAnimated: to mimic re-entrant item reload + [sectionController.collectionContext performBatchAnimated:YES updates:^{ + object.value = @6; // from @1 + + [sectionController.collectionContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + [sectionController.collectionContext deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(5, 3)]]; + [sectionController.collectionContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 6); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenPerformingUpdates_withoutSettingDataSource_thatCompletionBlockExecutes { + IGListCollectionView *collectionView = [[IGListCollectionView alloc] initWithFrame:self.window.frame collectionViewLayout:[UICollectionViewFlowLayout new]]; + [self.window addSubview:collectionView]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil workingRangeSize:0]; + adapter.collectionView = collectionView; + + self.dataSource.objects = @[ + genTestObject(@1, @1) + ]; + + XCTestExpectation *expectation = genExpectation; + + // call -performUpdatesAnimated: before we have set the data source + [adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + + // since the data source isnt set, we complete syncronously. dispatch_async simulates setting the data source + // in a different runloop from the completion block so it should be set by the time we make our subsequent + // -performUpdatesAnimated: call + dispatch_async(dispatch_get_main_queue(), ^{ + self.dataSource.objects = @[ + genTestObject(@1, @1), + genTestObject(@2, @2) + ]; + [adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + XCTAssertEqual([collectionView numberOfSections], 2); + [expectation fulfill]; + }]; + }); + }]; + + // setting the data source immediately queries it, since the collection view is also set + adapter.dataSource = self.dataSource; + // simulate display reloading data on the collection view + [collectionView layoutIfNeeded]; + + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenPerformingUpdates_withItemsMovingInBlocks_thatCollectionViewWorks { + [self setupWithObjects:@[ + genTestObject(@1, @0), + genTestObject(@2, @7), + genTestObject(@3, @8), + genTestObject(@4, @8), + genTestObject(@5, @8), + genTestObject(@6, @5), + genTestObject(@7, @8), + genTestObject(@8, @8), + genTestObject(@9, @8), + ]]; + + IGListCollectionView *collectionView = [[IGListCollectionView alloc] initWithFrame:self.window.frame collectionViewLayout:[UICollectionViewFlowLayout new]]; + [self.window addSubview:collectionView]; + IGListAdapterUpdater *updater = [IGListAdapterUpdater new]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:0]; + adapter.dataSource = self.dataSource; + adapter.collectionView = collectionView; + [collectionView layoutSubviews]; + + XCTAssertEqual([collectionView numberOfSections], 9); + + self.dataSource.objects = @[ + genTestObject(@1, @0), + genTestObject(@10, @5), + genTestObject(@11, @7), + genTestObject(@2, @7), + genTestObject(@3, @8), + genTestObject(@6, @5), // "moves" in front of 4, 5 but doesn't change index in array + genTestObject(@4, @8), + genTestObject(@5, @8), + genTestObject(@7, @8), + genTestObject(@8, @8), + ]; + + XCTestExpectation *expectation = genExpectation; + [adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + XCTAssertEqual([collectionView numberOfSections], 10); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenReleasingObjects_thatAssertDoesntFire { + [self setupWithObjects:@[ + genTestObject(@1, @1) + ]]; + + // if the adapter keeps a strong ref to self and uses an async method, this will hit asserts that a list item + // controller is nil. the adapter should be released and the completion block never called. + @autoreleasepool { + IGListAdapterUpdater *updater = [[IGListAdapterUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:2]; + adapter.collectionView = self.collectionView; + adapter.dataSource = self.dataSource; + [adapter performUpdatesAnimated:NO completion:^(BOOL finished) { + XCTAssertTrue(NO, @"Should not reach completion block for adapter"); + }]; + } + + self.collectionView = nil; + self.dataSource = nil; + + // queued after perform updates + XCTestExpectation *expectation = genExpectation; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenItemDeleted_withDisplayDelegate_thatDelegateReceivesDeletedItem { + [self setupWithObjects:@[ + genTestObject(@1, @1), + genTestObject(@2, @2), + ]]; + IGTestObject *object = self.dataSource.objects[0]; + + self.dataSource.objects = @[ + genTestObject(@2, @2), + ]; + + id mockDisplayHandler = [OCMockObject mockForProtocol:@protocol(IGListAdapterDelegate)]; + self.adapter.delegate = mockDisplayHandler; + + [[mockDisplayHandler expect] listAdapter:self.adapter didEndDisplayingObject:object atIndex:0]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) { + [mockDisplayHandler verify]; + XCTAssertTrue(finished2); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenItemReloaded_withDisplacingMutations_thatCollectionViewWorks { + [self setupWithObjects:@[ + genTestObject(@1, @1), + genTestObject(@2, @1), + genTestObject(@3, @1), + genTestObject(@4, @1), + genTestObject(@5, @1), + ]]; + + self.dataSource.objects = @[ + genTestObject(@1, @1), + genTestObject(@2, @2), // reloaded + genTestObject(@5, @2), // reloaded + genTestObject(@4, @2), // reloaded + genTestObject(@3, @1), + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + XCTAssertTrue(finished); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenCollectionViewAppears_thatWillDisplayEventsAreSent { + [self setupWithObjects:@[ + genTestObject(@1, @1), + genTestObject(@2, @2), + ]]; + IGTestDelegateController *ic1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + XCTAssertEqual(ic1.willDisplayCount, 1); + XCTAssertEqual(ic1.didEndDisplayCount, 0); + XCTAssertEqual([ic1.willDisplayCellIndexes countForObject:@0], 1); + XCTAssertEqual([ic1.didEndDisplayCellIndexes countForObject:@0], 0); + + IGTestDelegateController *ic2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + XCTAssertEqual(ic2.willDisplayCount, 1); + XCTAssertEqual(ic2.didEndDisplayCount, 0); + XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@0], 1); + XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@1], 1); + XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@0], 0); + XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@1], 0); +} + +- (void)test_whenAdapterUpdates_withItemUpdated_thatdidEndDisplayEventsAreSent { + [self setupWithObjects:@[ + genTestObject(@1, @1), + genTestObject(@2, @2), + ]]; + IGTestDelegateController *ic1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGTestDelegateController *ic2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + + self.dataSource.objects = @[ + genTestObject(@1, @1), + genTestObject(@2, @1), // reloaded w/ 1 cell removed + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + XCTAssertEqual(ic1.willDisplayCount, 1); + XCTAssertEqual(ic1.didEndDisplayCount, 0); + XCTAssertEqual([ic1.willDisplayCellIndexes countForObject:@0], 1); + XCTAssertEqual([ic1.didEndDisplayCellIndexes countForObject:@0], 0); + + XCTAssertEqual(ic2.willDisplayCount, 1); + XCTAssertEqual(ic2.didEndDisplayCount, 0); + XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@1], 1); + XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@1], 1); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenAdapterUpdates_withItemRemoved_thatdidEndDisplayEventsAreSent { + [self setupWithObjects:@[ + genTestObject(@1, @1), + genTestObject(@2, @2), + ]]; + IGTestDelegateController *ic1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGTestDelegateController *ic2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + + self.dataSource.objects = @[ + genTestObject(@1, @1) + ]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + XCTAssertEqual(ic1.willDisplayCount, 1); + XCTAssertEqual(ic1.didEndDisplayCount, 0); + XCTAssertEqual([ic1.willDisplayCellIndexes countForObject:@0], 1); + XCTAssertEqual([ic1.didEndDisplayCellIndexes countForObject:@0], 0); + + XCTAssertEqual(ic2.willDisplayCount, 1); + XCTAssertEqual(ic2.didEndDisplayCount, 1); + XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@0], 1); + XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@1], 1); + XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@0], 1); + XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@1], 1); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenAdapterUpdates_withEmptyItems_thatdidEndDisplayEventsAreSent { + [self setupWithObjects:@[ + genTestObject(@1, @1), + genTestObject(@2, @2), + ]]; + IGTestDelegateController *ic1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGTestDelegateController *ic2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + + self.dataSource.objects = @[]; + + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + XCTAssertEqual(ic1.didEndDisplayCount, 1); + XCTAssertEqual([ic1.didEndDisplayCellIndexes countForObject:@0], 1); + + XCTAssertEqual(ic2.didEndDisplayCount, 1); + XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@0], 1); + XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@1], 1); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenBatchUpdating_withCellQuery_thatCellIsNil { + __block BOOL executed = NO; + __weak __typeof__(self) weakSelf = self; + void (^block)(IGTestDelegateController *) = ^(IGTestDelegateController *ic) { + executed = YES; + XCTAssertNil([weakSelf.adapter cellForItemAtIndex:0 sectionController:ic]); + }; + self.dataSource.cellConfigureBlock = block; + + [self setupWithObjects:@[ + genTestObject(@1, @1), + genTestObject(@2, @1), + genTestObject(@3, @1), + ]]; + + // delete the last object from the original array + self.dataSource.objects = @[ + genTestObject(@1, @1), + genTestObject(@2, @1), + genTestObject(@4, @1), + genTestObject(@5, @1), + genTestObject(@6, @1), + ]; + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenBatchUpdating_withDuplicateIdentifiers_thatHaveDifferentValues_thatCollectionViewWorks { + [self setupWithObjects:@[ + // using string values IGTestDelegateController always returns 1 cell + genTestObject(@1, @"a"), + genTestObject(@2, @"a"), + genTestObject(@3, @"a"), + genTestObject(@4, @"b"), // problem item w/ key 4, value "b" + ]]; + + self.dataSource.objects = @[ + genTestObject(@1, @"a"), + genTestObject(@5, @"a"), + genTestObject(@6, @"a"), + genTestObject(@7, @"a"), + genTestObject(@4, @"a"), // key 4 but value "a", so this needs reloaded + genTestObject(@8, @"a"), + genTestObject(@4, @"b"), // key 4 but value didn't change + ]; + XCTestExpectation *expectation = genExpectation; + + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +@end diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m new file mode 100644 index 000000000..08d15e341 --- /dev/null +++ b/Tests/IGListAdapterTests.m @@ -0,0 +1,450 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import + +#import +#import + +#import "IGListAdapterInternal.h" +#import "IGListTestAdapterDataSource.h" +#import "IGListTestSection.h" +#import "IGTestSupplementarySource.h" + +@interface IGListAdapterTests : XCTestCase + +// infra does not hold a strong ref to collection view +@property (nonatomic, strong) IGListCollectionView *collectionView; +@property (nonatomic, strong) IGListAdapter *adapter; +@property (nonatomic, strong) IGListTestAdapterDataSource *dataSource; +@property (nonatomic, strong) UIWindow *window; + +@end + +@implementation IGListAdapterTests + +- (void)setUp { + [super setUp]; + + // minimum line spacing, item size, and minimum interim spacing are all set in IGListTestSection + self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + self.collectionView = [[IGListCollectionView alloc] initWithFrame:self.window.bounds collectionViewLayout:layout]; + + [self.window addSubview:self.collectionView]; + + // syncronous reloads so we dont have to do expectations or other nonsense + IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init]; + + self.dataSource = [[IGListTestAdapterDataSource alloc] init]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:updater + viewController:nil + workingRangeSize:0]; + self.adapter.collectionView = self.collectionView; + self.adapter.dataSource = self.dataSource; +} + +- (void)tearDown { + [super tearDown]; + self.window = nil; + self.collectionView = nil; + self.adapter = nil; + self.dataSource = nil; +} + +- (void)test_whenAdapterNotUpdated_withDataSourceUpdated_thatAdapterHasNoSectionControllers { + self.dataSource.objects = @[@0, @1, @2]; + XCTAssertNil([self.adapter sectionControllerForObject:@0]); + XCTAssertNil([self.adapter sectionControllerForObject:@1]); + XCTAssertNil([self.adapter sectionControllerForObject:@2]); +} + +- (void)test_whenAdapterUpdated_thatAdapterHasSectionControllers { + self.dataSource.objects = @[@0, @1, @2]; + [self.adapter performUpdatesAnimated:YES completion:nil]; + XCTAssertNotNil([self.adapter sectionControllerForObject:@0]); + XCTAssertNotNil([self.adapter sectionControllerForObject:@1]); + XCTAssertNotNil([self.adapter sectionControllerForObject:@2]); +} + +- (void)test_whenAdapterReloaded_thatAdapterHasSectionControllers { + self.dataSource.objects = @[@0, @1, @2]; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertNotNil([self.adapter sectionControllerForObject:@0]); + XCTAssertNotNil([self.adapter sectionControllerForObject:@1]); + XCTAssertNotNil([self.adapter sectionControllerForObject:@2]); +} + +- (void)test_whenAdapterUpdated_thatSectionControllerHasSection { + self.dataSource.objects = @[@0, @1, @2]; + [self.adapter performUpdatesAnimated:YES completion:nil]; + IGListSectionController * list = [self.adapter sectionControllerForObject:@1]; + XCTAssertEqual([self.adapter sectionForSectionController:list], 1); +} + +- (void)test_whenAdapterUpdated_withUnknownItem_thatSectionControllerHasNoSection { + self.dataSource.objects = @[@0, @1, @2]; + [self.adapter performUpdatesAnimated:YES completion:nil]; + IGListSectionController * randomList = [[IGListTestSection alloc] init]; + XCTAssertEqual([self.adapter sectionForSectionController:randomList], NSNotFound); +} + +- (void)test_whenQueryingAdapter_withUnknownItem_thatSectionControllerIsNil { + self.dataSource.objects = @[@0, @1, @2]; + [self.adapter performUpdatesAnimated:YES completion:nil]; + XCTAssertNil([self.adapter sectionControllerForObject:@3]); +} + +- (void)test_whenQueryingIndexPaths_withSectionController_thatPathsAreEqual { + self.dataSource.objects = @[@0, @1, @2]; + [self.adapter performUpdatesAnimated:YES completion:nil]; + IGListSectionController * second = [self.adapter sectionControllerForObject:@1]; + NSArray *paths0 = [self.adapter indexPathsFromSectionController:second + indexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(2, 4)] + adjustForUpdateBlock:NO]; + NSArray *expected = @[ + [NSIndexPath indexPathForItem:2 inSection:1], + [NSIndexPath indexPathForItem:3 inSection:1], + [NSIndexPath indexPathForItem:4 inSection:1], + [NSIndexPath indexPathForItem:5 inSection:1], + ]; + XCTAssertEqualObjects(paths0, expected); +} + +- (void)test_whenQueryingIndexPaths_insideBatchUpdateBlock_thatPathsAreEqual { + self.dataSource.objects = @[@0, @1, @2]; + [self.adapter performUpdatesAnimated:YES completion:nil]; + IGListSectionController * second = [self.adapter sectionControllerForObject:@1]; + + __block BOOL executed = NO; + [self.adapter performBatchAnimated:YES updates:^{ + NSArray *paths = [self.adapter indexPathsFromSectionController:second + indexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(2, 2)] + adjustForUpdateBlock:YES]; + NSArray *expected = @[ + [NSIndexPath indexPathForItem:2 inSection:1], + [NSIndexPath indexPathForItem:3 inSection:1], + ]; + XCTAssertEqualObjects(paths, expected); + + executed = YES; + } completion:nil]; + XCTAssertTrue(executed); +} + +- (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassName { + NSString *identifier = [self.adapter reusableViewIdentifierForClass:UICollectionViewCell.class]; + XCTAssertEqualObjects(identifier, @"UICollectionViewCell"); +} + +- (void)test_whenDataSourceChanges_thatBackgroundViewVisibilityChanges { + self.dataSource.objects = @[@1]; + UIView *background = [[UIView alloc] init]; + self.dataSource.backgroundView = background; + __block BOOL executed = NO; + [self.adapter reloadDataWithCompletion:^(BOOL finished) { + XCTAssertTrue(self.adapter.collectionView.backgroundView.hidden, @"Background view should be hidden"); + XCTAssertEqualObjects(background, self.adapter.collectionView.backgroundView, @"Background view not correctly assigned"); + + self.dataSource.objects = @[]; + [self.adapter reloadDataWithCompletion:^(BOOL finished2) { + XCTAssertFalse(self.adapter.collectionView.backgroundView.hidden, @"Background view should be visible"); + XCTAssertEqualObjects(background, self.adapter.collectionView.backgroundView, @"Background view not correctly assigned"); + executed = YES; + }]; + }]; + XCTAssertTrue(executed); +} + +- (void)test_whenReloadingData_thatNewSectionControllersAreCreated { + self.dataSource.objects = @[@0, @1, @2]; + [self.adapter reloadDataWithCompletion:nil]; + IGListSectionController *oldSectionController = [self.adapter sectionControllerForObject:@1]; + [self.adapter reloadDataWithCompletion:nil]; + IGListSectionController *newSectionController = [self.adapter sectionControllerForObject:@1]; + XCTAssertNotEqual(oldSectionController, newSectionController); +} + +- (void)test_whenSettingCollectionView_thenSettingDataSource_thatViewControllerIsSet { + self.dataSource.objects = @[@0, @1, @2]; + UIViewController *controller = [UIViewController new]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new] + viewController:controller + workingRangeSize:0]; + adapter.collectionView = self.collectionView; + adapter.dataSource = self.dataSource; + IGListSectionController *sectionController = [adapter sectionControllerForObject:@1]; + XCTAssertEqual(controller, sectionController.viewController); +} + +- (void)test_whenSettingCollectionView_thenSettingDataSource_thatCellExists { + self.dataSource.objects = @[@1]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new] + viewController:nil + workingRangeSize:0]; + adapter.collectionView = self.collectionView; + adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]); +} + +- (void)test_whenSettingDataSource_thenSettingCollectionView_thatCellExists { + self.dataSource.objects = @[@1]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new] + viewController:nil + workingRangeSize:0]; + adapter.dataSource = self.dataSource; + adapter.collectionView = self.collectionView; + [self.collectionView layoutIfNeeded]; + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]); +} + +- (void)test_whenChangingCollectionViews_thatCellsExist { + self.dataSource.objects = @[@1]; + IGListAdapterUpdater *updater = [[IGListAdapterUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:0]; + adapter.dataSource = self.dataSource; + adapter.collectionView = self.collectionView; + [self.collectionView layoutIfNeeded]; + XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]); + + IGListCollectionView *otherCollectionView = [[IGListCollectionView alloc] initWithFrame:self.collectionView.frame collectionViewLayout:self.collectionView.collectionViewLayout]; + adapter.collectionView = otherCollectionView; + [otherCollectionView layoutIfNeeded]; + XCTAssertNotNil([otherCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]); +} + +- (void)test_whenChangingCollectionViewsToACollectionViewInUseByAnotherAdapter_thatCollectionViewDelegateIsUpdated { + IGListTestAdapterDataSource *dataSource1 = [[IGListTestAdapterDataSource alloc] init]; + dataSource1.objects = @[@1]; + IGListAdapterUpdater *updater1 = [[IGListAdapterUpdater alloc] init]; + IGListAdapter *adapter1 = [[IGListAdapter alloc] initWithUpdater:updater1 viewController:nil workingRangeSize:0]; + adapter1.dataSource = dataSource1; + + IGListTestAdapterDataSource *dataSource2 = [[IGListTestAdapterDataSource alloc] init]; + dataSource1.objects = @[@1]; + IGListAdapterUpdater *updater2 = [[IGListAdapterUpdater alloc] init]; + IGListAdapter *adapter2 = [[IGListAdapter alloc] initWithUpdater:updater2 viewController:nil workingRangeSize:0]; + adapter1.dataSource = dataSource2; + + // associate collection view with adapter1 + adapter1.collectionView = self.collectionView; + XCTAssertEqual(self.collectionView.dataSource, adapter1); + + // associate collection view with adapter2 + adapter2.collectionView = self.collectionView; + XCTAssertEqual(self.collectionView.dataSource, adapter2); + + // associate collection view with adapter1 + adapter1.collectionView = self.collectionView; + XCTAssertEqual(self.collectionView.dataSource, adapter1); +} + +- (void)test_whenCellsExtendBeyondBounds_thatVisibleSectionControllersAreLimited { + // # of items for each object == [item integerValue], so @2 has 2 items (cells) + self.dataSource.objects = @[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12]; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertEqual([self.collectionView numberOfSections], 12); + NSArray *visibleSectionControllers = [self.adapter visibleSectionControllers]; + // UIWindow is 100x100, each cell is 100x10 so should have the following section/cell count: 1 + 2 + 3 + 4 = 10 (100 tall) + XCTAssertEqual(visibleSectionControllers.count, 4); + XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@1]]); + XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@2]]); + XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@3]]); + XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@4]]); +} + +- (void)test_whenCellsExtendBeyondBounds_thatVisibleCellsExistForSectionControllers { + self.dataSource.objects = @[@2, @3, @4, @5, @6]; + [self.adapter reloadDataWithCompletion:nil]; + id sectionController2 = [self.adapter sectionControllerForObject:@2]; + id sectionController3 = [self.adapter sectionControllerForObject:@3]; + id sectionController4 = [self.adapter sectionControllerForObject:@4]; + id sectionController5 = [self.adapter sectionControllerForObject:@5]; + id sectionController6 = [self.adapter sectionControllerForObject:@6]; + XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController2].count, 2); + XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController3].count, 3); + XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController4].count, 4); + XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController5].count, 1); + XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController6].count, 0); +} + +- (void)test_whenDataSourceAddsItems_thatEmptyViewBecomesVisible { + self.dataSource.objects = @[]; + UIView *background = [UIView new]; + self.dataSource.backgroundView = background; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertEqual(self.collectionView.backgroundView, background); + XCTAssertFalse(self.collectionView.backgroundView.hidden); + self.dataSource.objects = @[@2]; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertTrue(self.collectionView.backgroundView.hidden); +} + +- (void)test_whenScrollViewDelegateSet_thatDelegateReceivesEvents { + id mockDelegate = [OCMockObject mockForProtocol:@protocol(UIScrollViewDelegate)]; + + self.adapter.collectionViewDelegate = nil; + self.adapter.scrollViewDelegate = mockDelegate; + + [[mockDelegate expect] scrollViewDidScroll:self.collectionView]; + + [self.adapter scrollViewDidScroll:self.collectionView]; + + [mockDelegate verify]; +} + +- (void)test_whenCollectionViewDelegateSet_thatDelegateReceivesEvents { + // silence display handler asserts + self.dataSource.objects = @[@1, @2]; + [self.adapter reloadDataWithCompletion:nil]; + + id mockDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)]; + + self.adapter.collectionViewDelegate = mockDelegate; + self.adapter.scrollViewDelegate = nil; + + NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0]; + UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:path]; + [[mockDelegate expect] collectionView:self.collectionView didEndDisplayingCell:cell forItemAtIndexPath:path]; + + [self.adapter collectionView:self.collectionView didEndDisplayingCell:cell forItemAtIndexPath:path]; + + [mockDelegate verify]; +} + +- (void)test_whenCollectionViewDelegateSet_withScrollViewDelegateSet_thatDelegatesReceiveUniqueEvents { + // silence display handler asserts + self.dataSource.objects = @[@1, @2]; + [self.adapter reloadDataWithCompletion:nil]; + + id mockCollectionViewDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)]; + id mockScrollViewDelegate = [OCMockObject mockForProtocol:@protocol(UIScrollViewDelegate)]; + + self.adapter.collectionViewDelegate = mockCollectionViewDelegate; + self.adapter.scrollViewDelegate = mockScrollViewDelegate; + + NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0]; + UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:path]; + + [[mockScrollViewDelegate expect] scrollViewDidScroll:self.collectionView]; + + [[mockCollectionViewDelegate reject] scrollViewDidScroll:self.collectionView]; + [[mockCollectionViewDelegate expect] collectionView:self.collectionView didEndDisplayingCell:cell forItemAtIndexPath:path]; + + [self.adapter scrollViewDidScroll:self.collectionView]; + [self.adapter collectionView:self.collectionView didEndDisplayingCell:cell forItemAtIndexPath:path]; + + [mockScrollViewDelegate verify]; + [mockCollectionViewDelegate verify]; +} + +- (void)test_whenSupplementarySourceSupportsFooter_thatHeaderViewsAreNil { + self.dataSource.objects = @[@1, @2]; + [self.adapter reloadDataWithCompletion:nil]; + + IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new]; + supplementarySource.collectionContext = self.adapter; + supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionFooter]; + + IGListSectionController *controller = [self.adapter sectionControllerForObject:@1]; + controller.supplementaryViewSource = supplementarySource; + supplementarySource.sectionController = controller; + + [self.adapter performUpdatesAnimated:NO completion:nil]; + + XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]); + XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]); + XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]); + XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]); +} + +- (void)test_whenAdapterReleased_withSectionControllerStrongRefToCell_thatSectionControllersRelease { + __weak id weakCollectionView = nil, weakAdapter = nil, weakSectionController = nil; + + @autoreleasepool { + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + IGListCollectionView *collectionView = [[IGListCollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) + collectionViewLayout:layout]; + weakCollectionView = collectionView; + + IGListTestAdapterDataSource *dataSource = [[IGListTestAdapterDataSource alloc] init]; + dataSource.objects = @[@0, @1, @2]; + + IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:0]; + adapter.collectionView = collectionView; + adapter.dataSource = dataSource; + weakAdapter = adapter; + + IGListSectionController *sectionController = [adapter sectionControllerForObject:@1]; + weakSectionController = sectionController; + + // force the collection view to layout and generate cells + [collectionView layoutIfNeeded]; + + UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + XCTAssertNotNil(cell); + // strongly attach the cell to an section controller + objc_setAssociatedObject(sectionController, @"some_random_key", cell, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // weak refs should exist at this point + XCTAssertNotNil(weakCollectionView); + XCTAssertNotNil(weakAdapter); + XCTAssertNotNil(weakSectionController); + } + + XCTAssertNil(weakCollectionView); + XCTAssertNil(weakAdapter); + XCTAssertNil(weakSectionController); +} + +- (void)test_whenAdapterReleased_withSectionControllerStrongRefToCollectionView_thatSectionControllersRelease { + __weak id weakCollectionView = nil, weakAdapter = nil, weakSectionController = nil; + + @autoreleasepool { + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + IGListCollectionView *collectionView = [[IGListCollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) + collectionViewLayout:layout]; + weakCollectionView = collectionView; + + IGListTestAdapterDataSource *dataSource = [[IGListTestAdapterDataSource alloc] init]; + dataSource.objects = @[@0, @1, @2]; + + IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:0]; + adapter.collectionView = collectionView; + adapter.dataSource = dataSource; + weakAdapter = adapter; + + IGListSectionController *sectionController = [adapter sectionControllerForObject:@1]; + weakSectionController = sectionController; + + // force the collection view to layout and generate cells + [collectionView layoutIfNeeded]; + + // strongly attach the cell to an section controller + objc_setAssociatedObject(sectionController, @"some_random_key", collectionView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // weak refs should exist at this point + XCTAssertNotNil(weakCollectionView); + XCTAssertNotNil(weakAdapter); + XCTAssertNotNil(weakSectionController); + } + + XCTAssertNil(weakCollectionView); + XCTAssertNil(weakAdapter); + XCTAssertNil(weakSectionController); +} + +@end diff --git a/Tests/IGListAdapterUpdaterTests.m b/Tests/IGListAdapterUpdaterTests.m new file mode 100644 index 000000000..82d2f36dc --- /dev/null +++ b/Tests/IGListAdapterUpdaterTests.m @@ -0,0 +1,387 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGListAdapterUpdaterInternal.h" +#import "IGListTestUICollectionViewDataSource.h" + +#define genTestObject(k, v) [[IGSectionObject alloc] initWithKey:k value:v] + +#define genExpectation [self expectationWithDescription:NSStringFromSelector(_cmd)] +#define waitExpectation [self waitForExpectationsWithTimeout:15 handler:nil] + +@interface IGListAdapterUpdaterTests : XCTestCase + +@property (nonatomic, strong) UIWindow *window; +@property (nonatomic, strong) UICollectionView *collectionView; +@property (nonatomic, strong) IGListTestUICollectionViewDataSource *dataSource; +@property (nonatomic, strong) IGListAdapterUpdater *updater; +@property (nonatomic, strong) IGListObjectTransitionBlock updateBlock; + +@end + +@implementation IGListAdapterUpdaterTests + +- (void)setUp { + [super setUp]; + + self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + self.collectionView = [[UICollectionView alloc] initWithFrame:self.window.frame collectionViewLayout:layout]; + + [self.window addSubview:self.collectionView]; + + self.dataSource = [[IGListTestUICollectionViewDataSource alloc] initWithCollectionView:self.collectionView]; + self.updater = [[IGListAdapterUpdater alloc] init]; + __weak __typeof__(self) weakSelf = self; + self.updateBlock = ^(NSArray *obj) { + weakSelf.dataSource.sections = obj; + }; +} + +- (void)tearDown { + [super tearDown]; + + self.collectionView = nil; + self.dataSource = nil; + self.updater = nil; +} + +- (void)test_whenUpdatingWithNil_thatUpdaterHasNoChanges { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjects:nil animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + XCTAssertFalse([self.updater hasChanges]); +} + +- (void)test_whenUpdatingtoObjects_thatUpdaterHasChanges { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjects:@[@0] animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + XCTAssertTrue([self.updater hasChanges]); +} + +- (void)test_whenUpdatingfromObjects_thatUpdaterHasChanges { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:@[@0] toObjects:nil animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + XCTAssertTrue([self.updater hasChanges]); +} + +- (void)test_whenUpdatingtoObjects_withfromObjects_thatUpdaterHasChanges { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:@[@0] toObjects:@[@1] animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + XCTAssertTrue([self.updater hasChanges]); +} + +- (void)test_whenCleaningUpState_withChanges_thatUpdaterHasNoChanges { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjects:@[@0] animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + XCTAssertTrue([self.updater hasChanges]); + [self.updater cleanupState]; + XCTAssertFalse([self.updater hasChanges]); +} + +- (void)test_whenReloadingData_thatCollectionViewUpdates { + self.dataSource.sections = @[[IGSectionObject sectionWithObjects:@[]]]; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + XCTAssertEqual([self.collectionView numberOfSections], 1); + self.dataSource.sections = @[]; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + XCTAssertEqual([self.collectionView numberOfSections], 0); +} + +- (void)test_whenInsertingSection_thatCollectionViewUpdates { + NSArray *from = @[ + [IGSectionObject sectionWithObjects:@[]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + + self.dataSource.sections = from; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + XCTAssertEqual([self.collectionView numberOfSections], 1); + + XCTestExpectation *expectation = genExpectation; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenDeletingSection_thatCollectionViewUpdates { + NSArray *from = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]] + ]; + + self.dataSource.sections = from; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + XCTAssertEqual([self.collectionView numberOfSections], 2); + + XCTestExpectation *expectation = genExpectation; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + XCTAssertEqual([self.collectionView numberOfSections], 1); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenInsertingSection_withItemChanges_thatCollectionViewUpdates { + NSArray *from = @[ + [IGSectionObject sectionWithObjects:@[@0]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[@0, @1]], + [IGSectionObject sectionWithObjects:@[@0, @1]] + ]; + + self.dataSource.sections = from; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + XCTAssertEqual([self.collectionView numberOfSections], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); + + XCTestExpectation *expectation = genExpectation; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenInsertingSection_withDeletedSection_thatCollectionViewUpdates { + NSArray *from = @[ + [IGSectionObject sectionWithObjects:@[@0, @1, @2]], + [IGSectionObject sectionWithObjects:@[]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[@1, @1]], + [IGSectionObject sectionWithObjects:@[@0]], + [IGSectionObject sectionWithObjects:@[@0, @2, @3]] + ]; + + self.dataSource.sections = from; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + + XCTestExpectation *expectation = genExpectation; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenReloadingSections_thatCollectionViewUpdates { + self.dataSource.sections = @[ + [IGSectionObject sectionWithObjects:@[@0, @1]], + [IGSectionObject sectionWithObjects:@[@0, @1]] + ]; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); + + self.dataSource.sections = @[ + [IGSectionObject sectionWithObjects:@[@0, @1, @2]], + [IGSectionObject sectionWithObjects:@[@0, @1]] + ]; + [self.updater reloadCollectionView:self.collectionView sections:[NSIndexSet indexSetWithIndex:0]]; + + XCTAssertEqual([self.collectionView numberOfSections], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); +} + +- (void)test_whenCollectionViewNeedsLayout_thatPerformBatchUpdateWorks { + NSArray *from = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]] + ]; + + self.dataSource.sections = from; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + + // the collection view has been setup with 1 section and now needs layout + // calling performBatchUpdates: on a collection view needing layout will force layout + // we need to ensure that our data source is not changed until the update block is executed + [self.collectionView setNeedsLayout]; + + XCTestExpectation *expectation = genExpectation; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + XCTAssertEqual([self.collectionView numberOfSections], 1); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenUpdatesAreReentrant_thatUpdatesExecuteSerially { + NSArray *from = @[ + [IGSectionObject sectionWithObjects:@[]], + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + ]; + + self.dataSource.sections = from; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + + __block NSInteger completionCounter = 0; + + XCTestExpectation *expectation = genExpectation; + void (^preUpdateBlock)() = ^{ + NSArray *anotherTo = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:anotherTo animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + completionCounter++; + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual(completionCounter, 2); + [expectation fulfill]; + }]; + }; + + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:^(NSArray *toObjects) { + // executing this block within the updater is just before performBatchUpdates: are applied + // should be able to queue another update here, similar to an update being queued between it beginning and executing + // the performBatchUpdates: block + preUpdateBlock(); + + self.dataSource.sections = toObjects; + } completion:^(BOOL finished) { + completionCounter++; + XCTAssertEqual(completionCounter, 1); + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenQueuingItemUpdates_thatUpdaterHasChanges { + [self.updater performUpdateWithCollectionView:self.collectionView animated:YES itemUpdates:^{} completion:nil]; + XCTAssertTrue([self.updater hasChanges]); +} + +- (void)test_whenOnlyQueueingItemUpdates_thatUpdateBlockExecutes { + XCTestExpectation *expectation = genExpectation; + [self.updater performUpdateWithCollectionView:self.collectionView animated:YES itemUpdates:^{ + // expectation should be triggered. test failure is a timeout + [expectation fulfill]; + } completion:nil]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenQueueingItemUpdates_withBatchUpdate_thatItemUpdateBlockExecutes { + __block BOOL itemUpdateBlockExecuted = NO; + __block BOOL sectionUpdateBlockExecuted = NO; + + [self.updater performUpdateWithCollectionView:self.collectionView + fromObjects:nil + toObjects:@[[IGSectionObject sectionWithObjects:@[@1]]] + animated:YES objectTransitionBlock:^(NSArray * toObjects) { + self.dataSource.sections = toObjects; + sectionUpdateBlockExecuted = YES; + } + completion:nil]; + + XCTestExpectation *expectation = genExpectation; + [self.updater performUpdateWithCollectionView:self.collectionView animated:YES itemUpdates:^{ + itemUpdateBlockExecuted = YES; + } completion:^(BOOL finished) { + // test in the item completion block that the SECTION operations have been performed + XCTAssertEqual([self.collectionView numberOfSections], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); + XCTAssertTrue(itemUpdateBlockExecuted); + XCTAssertTrue(sectionUpdateBlockExecuted); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenItemsMoveAndUpdate_thatCollectionViewWorks { + NSArray *from = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + ]; + + // change the number of items in the section, which a move would be unable to handle and would throw + // keep the same pointers so that the objects are equal + [from[2] setObjects:@[@1]]; + [from[0] setObjects:@[@1, @1]]; + [from[1] setObjects:@[@1, @1, @1]]; + + // rearrange the modified objects + NSArray *to = @[ + from[2], + from[0], + from[1] + ]; + + self.dataSource.sections = from; + [self.updater performReloadDataWithCollectionView:self.collectionView]; + + // without moves as inserts, we would assert b/c the # of items in each section changes + self.updater.movesAsDeletesInserts = YES; + + XCTestExpectation *expectation = genExpectation; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 3); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenConvertingReloads_withoutChanges_thatOriginalIndexUsed { + NSArray *from = @[]; + NSArray *to = @[]; + IGListIndexSetResult *result = IGListDiff(from, to, IGListDiffEquality); + NSMutableIndexSet *reloads = [result.updates mutableCopy]; + [reloads addIndex:2]; + NSMutableIndexSet *deletes = [result.deletes mutableCopy]; + NSMutableIndexSet *inserts = [result.inserts mutableCopy]; + convertReloadToDeleteInsert(reloads, deletes, inserts, result, from); + XCTAssertEqual(reloads.count, 0); + XCTAssertEqual(deletes.count, 1); + XCTAssertEqual(inserts.count, 1); + XCTAssertTrue([deletes containsIndex:2]); + XCTAssertTrue([inserts containsIndex:2]); +} + +- (void)test_whenConvertingReloads_withChanges_thatIndexMoves { + NSArray *from = @[@1, @2, @3]; + NSArray *to = @[@3, @2, @1]; + IGListIndexSetResult *result = IGListDiff(from, to, IGListDiffEquality); + NSMutableIndexSet *reloads = [result.updates mutableCopy]; + [reloads addIndex:2]; + NSMutableIndexSet *deletes = [result.deletes mutableCopy]; + NSMutableIndexSet *inserts = [result.inserts mutableCopy]; + convertReloadToDeleteInsert(reloads, deletes, inserts, result, from); + XCTAssertEqual(reloads.count, 0); + XCTAssertEqual(deletes.count, 1); + XCTAssertEqual(inserts.count, 1); + XCTAssertTrue([deletes containsIndex:2]); + XCTAssertTrue([inserts containsIndex:0]); +} + +@end diff --git a/Tests/IGListBatchUpdateDataTests.m b/Tests/IGListBatchUpdateDataTests.m new file mode 100644 index 000000000..6f973a366 --- /dev/null +++ b/Tests/IGListBatchUpdateDataTests.m @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGListBatchUpdateData.h" + +// IGListMoveIndexInternal.h +@interface IGListMoveIndex (Private) +- (instancetype)initWithFrom:(NSUInteger)from to:(NSUInteger)to; +@end + +@interface IGListBatchUpdateDataTests : XCTestCase + +@end + +@implementation IGListBatchUpdateDataTests + +static NSIndexSet *indexSet(NSArray *arr) { + NSMutableIndexSet *set = [NSMutableIndexSet new]; + for (NSNumber *n in arr) { + [set addIndex:[n integerValue]]; + } + return set; +} + +static NSIndexPath *newPath(NSUInteger section, NSUInteger item) { + return [NSIndexPath indexPathForItem:item inSection:section]; +} + +static IGListMoveIndex *newMove(NSUInteger from, NSUInteger to) { + return [[IGListMoveIndex alloc] initWithFrom:from to:to]; +} + +- (void)test_whenEmptyUpdates_thatResultExists { + IGListBatchUpdateData *result = [[IGListBatchUpdateData alloc] initWithInsertSections:indexSet(@[]) + deleteSections:indexSet(@[]) + moveSections:[NSSet new] + insertIndexPaths:[NSSet new] + deleteIndexPaths:[NSSet new] + reloadIndexPaths:[NSSet new]]; + XCTAssertNotNil(result); +} + +- (void)test_whenUpdatesAreClean_thatResultMatches { + IGListBatchUpdateData *result = [[IGListBatchUpdateData alloc] initWithInsertSections:indexSet(@[@0, @1]) + deleteSections:indexSet(@[@5]) + moveSections:[NSSet setWithArray:@[newMove(3, 4)]] + insertIndexPaths:[NSSet setWithArray:@[newPath(0, 0)]] + deleteIndexPaths:[NSSet setWithArray:@[newPath(1, 0)]] + reloadIndexPaths:[NSSet setWithArray:@[newPath(2, 0)]]]; + XCTAssertEqualObjects(result.insertSections, indexSet(@[@0, @1])); + XCTAssertEqualObjects(result.deleteSections, indexSet(@[@5])); + XCTAssertEqualObjects(result.moveSections, [NSSet setWithArray:@[newMove(3, 4)]]); + XCTAssertEqualObjects(result.insertIndexPaths, [NSSet setWithArray:@[newPath(0, 0)]]); + XCTAssertEqualObjects(result.deleteIndexPaths, [NSSet setWithArray:@[newPath(1, 0)]]); + XCTAssertEqualObjects(result.reloadIndexPaths, [NSSet setWithArray:@[newPath(2, 0)]]); +} + +- (void)test_whenReloadingItems_withSectionMove_thatResultConvertsConflicts_toDeletesAndInserts { + NSArray *reloads = @[ + newPath(2, 0), + newPath(2, 1), + newPath(0, 0) + ]; + IGListBatchUpdateData *result = [[IGListBatchUpdateData alloc] initWithInsertSections:indexSet(@[]) + deleteSections:indexSet(@[]) + moveSections:[NSSet setWithArray:@[newMove(2, 4)]] + insertIndexPaths:[NSSet new] + deleteIndexPaths:[NSSet new] + reloadIndexPaths:[NSSet setWithArray:reloads]]; + XCTAssertEqualObjects(result.insertSections, indexSet(@[@4])); + XCTAssertEqualObjects(result.deleteSections, indexSet(@[@2])); + XCTAssertEqualObjects(result.reloadIndexPaths, [NSSet setWithArray:@[newPath(0, 0)]]); + XCTAssertEqual(result.moveSections.count, 0); +} + +- (void)test_whenReloadingItem_withSectionDelete_thatDeleteWins { + IGListBatchUpdateData *result = [[IGListBatchUpdateData alloc] initWithInsertSections:indexSet(@[]) + deleteSections:indexSet(@[@2]) + moveSections:[NSSet new] + insertIndexPaths:[NSSet new] + deleteIndexPaths:[NSSet new] + reloadIndexPaths:[NSSet setWithArray:@[newPath(2, 0)]]]; + XCTAssertEqualObjects(result.deleteSections, indexSet(@[@2])); + XCTAssertEqual(result.reloadIndexPaths.count, 0); +} + +- (void)test_whenMovingSections_withItemDeletes_thatResultConvertsConflicts_toDeletesAndInserts { + IGListBatchUpdateData *result = [[IGListBatchUpdateData alloc] initWithInsertSections:indexSet(@[]) + deleteSections:indexSet(@[]) + moveSections:[NSSet setWithArray:@[newMove(2, 4)]] + insertIndexPaths:[NSSet new] + deleteIndexPaths:[NSSet setWithArray:@[newPath(2, 0), newPath(3, 4)]] + reloadIndexPaths:[NSSet new]]; + XCTAssertEqualObjects(result.insertSections, indexSet(@[@4])); + XCTAssertEqualObjects(result.deleteSections, indexSet(@[@2])); + XCTAssertEqualObjects(result.deleteIndexPaths, [NSSet setWithArray:@[newPath(3, 4)]]); + XCTAssertEqual(result.moveSections.count, 0); +} + +- (void)test_whenMovingSections_withItemInserts_thatResultConvertsConflicts_toDeletesAndInserts { + IGListBatchUpdateData *result = [[IGListBatchUpdateData alloc] initWithInsertSections:indexSet(@[]) + deleteSections:indexSet(@[]) + moveSections:[NSSet setWithArray:@[newMove(2, 4)]] + insertIndexPaths:[NSSet setWithArray:@[newPath(4, 0), newPath(3, 4)]] + deleteIndexPaths:[NSSet new] + reloadIndexPaths:[NSSet new]]; + XCTAssertEqualObjects(result.insertSections, indexSet(@[@4])); + XCTAssertEqualObjects(result.deleteSections, indexSet(@[@2])); + XCTAssertEqualObjects(result.insertIndexPaths, [NSSet setWithArray:@[newPath(3, 4)]]); + XCTAssertEqual(result.moveSections.count, 0); +} + +@end diff --git a/Tests/IGListDiffSwiftTests.swift b/Tests/IGListDiffSwiftTests.swift new file mode 100644 index 000000000..ee80f6583 --- /dev/null +++ b/Tests/IGListDiffSwiftTests.swift @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +import XCTest +import IGListKit + +// conforms to IGListDiffable via NSObject (IGDKCommon) in IGDKCommon.h +class ObjCClass: NSObject { + +} + +class SwiftClass: IGListDiffable { + + let id: Int + let value: String + + init(id: Int, value: String) { + self.id = id + self.value = value + } + + @objc func diffIdentifier() -> NSObjectProtocol { + return NSNumber(int: Int32(id)) + } + + @objc func isEqual(object: AnyObject?) -> Bool { + if let object = object as? SwiftClass { + return id == object.id && value == object.value + } + return false + } + +} + +class IGDiffingSwiftTests: XCTestCase { + + func testConformance() { + XCTAssertTrue(ObjCClass.conformsToProtocol(IGListDiffable)) + } + + func testDiffingStrings() { + let o = ["a", "b", "c"] + let n = ["a", "c", "d"] + let result = IGListDiff(o, n, .Equality) + XCTAssertEqual(result.deletes, NSIndexSet(index: 1)) + XCTAssertEqual(result.inserts, NSIndexSet(index: 2)) + XCTAssertEqual(result.moves.count, 0) + XCTAssertEqual(result.updates.count, 0) + } + + func testDiffingNumbers() { + let o = [0, 1, 2] + let n = [0, 2, 4] + let result = IGListDiff(o, n, .Equality) + XCTAssertEqual(result.deletes, NSIndexSet(index: 1)) + XCTAssertEqual(result.inserts, NSIndexSet(index: 2)) + XCTAssertEqual(result.moves.count, 0) + XCTAssertEqual(result.updates.count, 0) + } + + func testDiffingSwiftClass() { + let o = [SwiftClass(id: 0, value: "a"), SwiftClass(id: 1, value: "b"), SwiftClass(id: 2, value: "c")] + let n = [SwiftClass(id: 0, value: "a"), SwiftClass(id: 2, value: "c"), SwiftClass(id: 4, value: "d")] + let result = IGListDiff(o, n, .Equality) + XCTAssertEqual(result.deletes, NSIndexSet(index: 1)) + XCTAssertEqual(result.inserts, NSIndexSet(index: 2)) + XCTAssertEqual(result.moves.count, 0) + XCTAssertEqual(result.updates.count, 0) + } + + func testDiffingSwiftClassPointerComparison() { + let o = [SwiftClass(id: 0, value: "a"), SwiftClass(id: 1, value: "b"), SwiftClass(id: 2, value: "c")] + let n = [SwiftClass(id: 0, value: "a"), SwiftClass(id: 2, value: "c"), SwiftClass(id: 4, value: "d")] + let result = IGListDiff(o, n, .PointerPersonality) + XCTAssertEqual(result.deletes, NSIndexSet(index: 1)) + XCTAssertEqual(result.inserts, NSIndexSet(index: 2)) + XCTAssertEqual(result.moves.count, 0) + XCTAssertEqual(result.updates.count, 2) + } + + func testDiffingSwiftClassWithUpdates() { + let o = [SwiftClass(id: 0, value: "a"), SwiftClass(id: 1, value: "b"), SwiftClass(id: 2, value: "c")] + let n = [SwiftClass(id: 0, value: "b"), SwiftClass(id: 1, value: "b"), SwiftClass(id: 2, value: "b")] + let result = IGListDiff(o, n, .Equality) + XCTAssertEqual(result.deletes.count, 0) + XCTAssertEqual(result.inserts.count, 0) + XCTAssertEqual(result.moves.count, 0) + XCTAssertEqual(result.updates.count, 2) + } +} diff --git a/Tests/IGListDiffTests.h b/Tests/IGListDiffTests.h new file mode 100644 index 000000000..e4c452511 --- /dev/null +++ b/Tests/IGListDiffTests.h @@ -0,0 +1,9 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +// Exposed for internal benchmark tests +@interface IGListDiffTests : XCTestCase + +@end + diff --git a/Tests/IGListDiffTests.m b/Tests/IGListDiffTests.m new file mode 100644 index 000000000..4359ec528 --- /dev/null +++ b/Tests/IGListDiffTests.m @@ -0,0 +1,369 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListDiffTests.h" + +#import +#import + +#import +#import + +#import "IGListMoveIndexInternal.h" +#import "IGListMoveIndexPathInternal.h" +#import "IGTestObject.h" + +#define genIndexPath(i, s) [NSIndexPath indexPathForItem:i inSection:s] +#define genTestObject(k, d) [[IGTestObject alloc] initWithKey:k value:d] + +#define IGAssertContains(collection, object) do {\ + id haystack = collection; id needle = object; \ + XCTAssertTrue([haystack containsObject:needle], @"%@ does not contain %@", haystack, needle); \ + } while(0) + + +static NSIndexSet *indexSetWithIndexes(NSArray *indexes) { + NSMutableIndexSet *indexset = [NSMutableIndexSet new]; + for (NSNumber *i in indexes) { + [indexset addIndex:i.integerValue]; + } + return indexset; +} + + +static NSArray *sorted(NSArray *arr) { + return [arr sortedArrayUsingSelector:@selector(compare:)]; +} + + +@interface IGListIndexSetResult (UnitTests) +- (NSUInteger)changeCount; +@end + +@implementation IGListIndexSetResult (UnitTests) +- (NSUInteger)changeCount { + return self.inserts.count + self.deletes.count + self.moves.count + self.updates.count; +} +@end + + +@interface IGListIndexPathResult (UnitTests) +- (NSUInteger)changeCount; +@end + +@implementation IGListIndexPathResult (UnitTests) +- (NSUInteger)changeCount { + return self.inserts.count + self.deletes.count + self.moves.count + self.updates.count; +} +@end + + +@implementation IGListDiffTests + +- (void)test_whenDiffingEmptyArrays_thatResultHasNoChanges { + NSArray *o = @[]; + NSArray *n = @[]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertFalse([result hasChanges]); +} + +- (void)test_whenDiffingFromEmptyArray_thatResultHasChanges { + NSArray *o = @[]; + NSArray *n = @[@1]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertEqualObjects(result.inserts, [NSIndexSet indexSetWithIndex:0]); + XCTAssertEqual([result changeCount], 1); +} + +- (void)test_whenDiffingToEmptyArray_thatResultHasChanges { + NSArray *o = @[@1]; + NSArray *n = @[]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertEqualObjects(result.deletes, [NSIndexSet indexSetWithIndex:0]); + XCTAssertEqual([result changeCount], 1); +} + +- (void)test_whenSwappingObjects_thatResultHasMoves { + NSArray *o = @[@1, @2]; + NSArray *n = @[@2, @1]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + NSArray *expected = @[ + [[IGListMoveIndex alloc] initWithFrom:0 to:1], + [[IGListMoveIndex alloc] initWithFrom:1 to:0], + ]; + NSArray *sortedMoves = sorted(result.moves); + XCTAssertEqualObjects(sortedMoves, expected); + XCTAssertEqual([result changeCount], 2); +} + +- (void)test_whenMovingObjectsTogether_thatResultHasMoves { + // "trick" is having multiple @3s + NSArray *o = @[@1, @2, @3, @3, @4]; + NSArray *n = @[@2, @3, @1, @3, @4]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + IGAssertContains(result.moves, [[IGListMoveIndex alloc] initWithFrom:1 to:0]); + IGAssertContains(result.moves, [[IGListMoveIndex alloc] initWithFrom:0 to:2]); +} + +- (void)test_whenDiffingWordsFromPaper_withIndexPaths_thatDeletesMatchPaper { + // http://dl.acm.org/citation.cfm?id=359467&dl=ACM&coll=DL&CFID=529464736&CFTOKEN=43088172 + NSString *oString = @"much writing is like snow , a mass of long words and phrases falls upon the relevant facts covering up the details ."; + NSString *nString = @"a mass of latin words falls upon the relevant facts like soft snow , covering up the details ."; + NSArray *o = [oString componentsSeparatedByString:@" "]; + NSArray *n = [nString componentsSeparatedByString:@" "]; + IGListIndexPathResult *result = IGListDiffPaths(0, 0, o, n, IGListDiffEquality); + NSArray *expected = @[genIndexPath(0, 0), genIndexPath(1, 0), genIndexPath(2, 0), genIndexPath(9, 0), genIndexPath(11, 0), genIndexPath(12, 0)]; + XCTAssertEqualObjects(result.deletes, expected); +} + +- (void)test_whenDiffingWordsFromPaper_withIndexPaths_thatInsertsMatchPaper { + // http://dl.acm.org/citation.cfm?id=359467&dl=ACM&coll=DL&CFID=529464736&CFTOKEN=43088172 + NSString *oString = @"much writing is like snow , a mass of long words and phrases falls upon the relevant facts covering up the details ."; + NSString *nString = @"a mass of latin words falls upon the relevant facts like soft snow , covering up the details ."; + NSArray *o = [oString componentsSeparatedByString:@" "]; + NSArray *n = [nString componentsSeparatedByString:@" "]; + IGListIndexPathResult *result = IGListDiffPaths(0, 0, o, n, IGListDiffEquality); + NSArray *expected = @[genIndexPath(3, 0), genIndexPath(11, 0)]; + XCTAssertEqualObjects(result.inserts, expected); +} + +- (void)test_whenSwappingObjects_withIndexPaths_thatResultHasMoves { + NSArray *o = @[@1, @2, @3, @4]; + NSArray *n = @[@2, @4, @5, @3]; + IGListIndexPathResult *result = IGListDiffPaths(0, 0, o, n, IGListDiffEquality); + NSArray *expected = @[ + [[IGListMoveIndexPath alloc] initWithFrom:genIndexPath(2, 0) to:genIndexPath(3, 0)], + [[IGListMoveIndexPath alloc] initWithFrom:genIndexPath(3, 0) to:genIndexPath(1, 0)], + ]; + NSArray *sortedMoves = sorted(result.moves); + XCTAssertEqualObjects(sortedMoves, expected); +} + +- (void)test_whenObjectEqualityChanges_thatResultHasUpdates { + NSArray *o = @[ + genTestObject(@"0", @0), + genTestObject(@"1", @1), + genTestObject(@"2", @2), + ]; + NSArray *n = @[ + genTestObject(@"0", @0), + genTestObject(@"1", @3), // value updated from @1 to @3 + genTestObject(@"2", @2), + ]; + IGListIndexPathResult *result = IGListDiffPaths(0, 0, o, n, IGListDiffEquality); + NSArray *expected = @[genIndexPath(1, 0)]; + XCTAssertEqualObjects(result.updates, expected); +} + +- (void)test_whenDiffingWordsFromPaper_thatInsertsMatchPaper { + // http://dl.acm.org/citation.cfm?id=359467&dl=ACM&coll=DL&CFID=529464736&CFTOKEN=43088172 + NSString *oString = @"much writing is like snow , a mass of long words and phrases falls upon the relevant facts covering up the details ."; + NSString *nString = @"a mass of latin words falls upon the relevant facts like soft snow , covering up the details ."; + NSArray *o = [oString componentsSeparatedByString:@" "]; + NSArray *n = [nString componentsSeparatedByString:@" "]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + NSIndexSet *expectedInserts = indexSetWithIndexes(@[@3, @11]); + XCTAssertEqualObjects(result.inserts, expectedInserts); +} + +- (void)test_whenDiffingWordsFromPaper_thatDeletesMatchPaper { + // http://dl.acm.org/citation.cfm?id=359467&dl=ACM&coll=DL&CFID=529464736&CFTOKEN=43088172 + NSString *oString = @"much writing is like snow , a mass of long words and phrases falls upon the relevant facts covering up the details ."; + NSString *nString = @"a mass of latin words falls upon the relevant facts like soft snow , covering up the details ."; + NSArray *o = [oString componentsSeparatedByString:@" "]; + NSArray *n = [nString componentsSeparatedByString:@" "]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + NSIndexSet *expectedDeletes = indexSetWithIndexes(@[@0, @1, @2, @9, @11, @12]); + XCTAssertEqualObjects(result.deletes, expectedDeletes); +} + +- (void)test_whenDeletingItems_withInserts_withMoves_thatResultHasInsertsMovesAndDeletes { + NSArray *o = @[@0, @1, @2, @3, @4, @5, @6, @7, @8]; + NSArray *n = @[@0, @2, @3, @4, @7, @6, @9, @5, @10]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + NSIndexSet *expectedDeletes = indexSetWithIndexes(@[@1, @8]); + NSIndexSet *expectedInserts = indexSetWithIndexes(@[@6, @8]); + NSArray *expectedMoves = @[ + [[IGListMoveIndex alloc] initWithFrom:5 to:7], + [[IGListMoveIndex alloc] initWithFrom:7 to:4], + ]; + NSArray *sortedMoves = sorted(result.moves); + XCTAssertEqualObjects(result.deletes, expectedDeletes); + XCTAssertEqualObjects(result.inserts, expectedInserts); + XCTAssertEqualObjects(sortedMoves, expectedMoves); +} + +- (void)test_whenMovingItems_withEqualityChanges_thatResultsHasMovesAndUpdates { + NSArray *o = @[ + genTestObject(@"0", @0), + genTestObject(@"1", @1), + genTestObject(@"2", @2), + ]; + + // objects 0 and 2 are swapped and object at original index 2 has its data changed to @3 + NSArray *n = @[ + genTestObject(@"2", @3), + genTestObject(@"1", @1), + genTestObject(@"0", @0), + ]; + + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + NSArray *expectedMoves = @[ + [[IGListMoveIndex alloc] initWithFrom:0 to:2], + [[IGListMoveIndex alloc] initWithFrom:2 to:0], + ]; + NSIndexSet *expectedUpdates = [NSIndexSet indexSetWithIndex:2]; + NSArray *sortedMoves = sorted(result.moves); + XCTAssertEqualObjects(result.updates, expectedUpdates); + XCTAssertEqualObjects(sortedMoves, expectedMoves); +} + +- (void)test_whenDiffingPointers_withObjectCopy_thatResultHasUpdate { + NSArray *o = @[ + genTestObject(@"0", @0), + genTestObject(@"1", @1), + genTestObject(@"2", @2), + ]; + NSArray *n = @[ + o[0], + [o[1] copy], // new pointer + o[2], + ]; + IGListIndexPathResult *result = IGListDiffPaths(0, 0, o, n, IGListDiffPointerPersonality); + NSArray *expected = @[genIndexPath(1, 0)]; + XCTAssertEqualObjects(result.updates, expected); +} + +- (void)test_whenDiffingPointers_withSameObjects_thatResultHasNoChanges { + NSArray *o = @[ + genTestObject(@"0", @0), + genTestObject(@"1", @1), + genTestObject(@"2", @2), + ]; + NSArray *n = [o copy]; + IGListIndexPathResult *result = IGListDiffPaths(0, 0, o, n, IGListDiffPointerPersonality); + XCTAssertFalse([result hasChanges]); +} + +- (void)test_whenDeletingObjects_withArrayOfEqualObjects_thatChangeCountMatches { + NSArray *o = @[@"dog", @"dog", @"dog", @"dog"]; + NSArray *n = @[@"dog", @"dog"]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + // there is a "flaw" in the algorithm that cannot detect bulk ops when they are all the same object + // confirm that the results are at least correct + XCTAssertEqual(o.count + result.inserts.count - result.deletes.count, 2); +} + +- (void)test_whenInsertingObjects_withArrayOfEqualObjects_thatChangeCountMatches { + NSArray *o = @[@"dog", @"dog"]; + NSArray *n = @[@"dog", @"dog", @"dog", @"dog"]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + // there is a "flaw" in the algorithm that cannot detect bulk ops when they are all the same object + // confirm that the results are at least correct + XCTAssertEqual(o.count + result.inserts.count - result.deletes.count, 4); +} + +- (void)test_whenInsertingObject_withOldArrayHavingMultiples_thatChangeCountMatches { + NSArray *o = @[[NSObject new], [NSObject new], [NSObject new], @49, @33, @"cat", @"cat", @0, @14]; + NSMutableArray *n = [o mutableCopy]; + [n insertObject:@"cat" atIndex:5]; // 3 cats in a row + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertEqual(o.count + result.inserts.count - result.deletes.count, 10); +} + +- (void)test_whenMovingDuplicateObjects_thatChangeCountMatches { + NSArray *o = @[@1, @20, @14, [NSObject new], @"cat", [NSObject new], @4, @"dog", @"cat", @"cat", @"fish", [NSObject new], @"fish", [NSObject new]]; + NSArray *n = @[@1, @28, @14, @"cat", @"cat", @4, @"dog", o[3], @"cat", @"fish", o[11], @"fish", o[13]]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertEqual(o.count + result.inserts.count - result.deletes.count, n.count); +} + +- (void)test_whenDiffingDuplicatesAtTail_withDuplicateAtHead_thatResultHasNoChanges { + NSArray *o = @[@"cat", @1, @2, @3, @"cat"]; + NSArray *n = @[@"cat", @1, @2, @3, @"cat"]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertFalse([result hasChanges]); +} + +- (void)test_whenDuplicateObjects_thatMovesAreUnique { + NSArray *o = @[@"cat", [NSObject new], @"dog", @"dog", [NSObject new], [NSObject new], @"cat", @65]; + NSArray *n = @[@"cat", o[1], @"dog", o[4], @"dog", o[5], @"cat", @"cat", @"fish", @65]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertEqual([[NSSet setWithArray:[[result moves] valueForKeyPath:@"from"]] count], [result.moves count]); +} + +- (void)test_whenMovingObjectShiftsOthers_thatMovesContainRequiredMoves { + NSArray *o = @[@1, @2, @3, @4, @5, @6, @7]; + NSArray *n = @[@1, @4, @5, @2, @3, @6, @7]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + IGAssertContains(result.moves, [[IGListMoveIndex alloc] initWithFrom:3 to:1]); + IGAssertContains(result.moves, [[IGListMoveIndex alloc] initWithFrom:1 to:3]); +} + +- (void)test_whenDiffing_thatOldIndexesMatch { + NSArray *o = @[@1, @2, @3, @4, @5, @6, @7]; + NSArray *n = @[@2, @9, @3, @1, @5, @6, @8]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertEqual([result oldIndexForIdentifier:@1], 0); + XCTAssertEqual([result oldIndexForIdentifier:@2], 1); + XCTAssertEqual([result oldIndexForIdentifier:@3], 2); + XCTAssertEqual([result oldIndexForIdentifier:@4], 3); + XCTAssertEqual([result oldIndexForIdentifier:@5], 4); + XCTAssertEqual([result oldIndexForIdentifier:@6], 5); + XCTAssertEqual([result oldIndexForIdentifier:@7], 6); + XCTAssertEqual([result oldIndexForIdentifier:@8], NSNotFound); + XCTAssertEqual([result oldIndexForIdentifier:@9], NSNotFound); +} + +- (void)test_whenDiffing_thatNewIndexesMatch { + NSArray *o = @[@1, @2, @3, @4, @5, @6, @7]; + NSArray *n = @[@2, @9, @3, @1, @5, @6, @8]; + IGListIndexSetResult *result = IGListDiff(o, n, IGListDiffEquality); + XCTAssertEqual([result newIndexForIdentifier:@1], 3); + XCTAssertEqual([result newIndexForIdentifier:@2], 0); + XCTAssertEqual([result newIndexForIdentifier:@3], 2); + XCTAssertEqual([result newIndexForIdentifier:@4], NSNotFound); + XCTAssertEqual([result newIndexForIdentifier:@5], 4); + XCTAssertEqual([result newIndexForIdentifier:@6], 5); + XCTAssertEqual([result newIndexForIdentifier:@7], NSNotFound); + XCTAssertEqual([result newIndexForIdentifier:@8], 6); + XCTAssertEqual([result newIndexForIdentifier:@9], 1); +} + +- (void)test_whenDiffing_thatOldIndexPathsMatch { + NSArray *o = @[@1, @2, @3, @4, @5, @6, @7]; + NSArray *n = @[@2, @9, @3, @1, @5, @6, @8]; + IGListIndexPathResult *result = IGListDiffPaths(0, 1, o, n, IGListDiffEquality); + XCTAssertEqualObjects([result oldIndexPathForIdentifier:@1], [NSIndexPath indexPathForItem:0 inSection:0]); + XCTAssertEqualObjects([result oldIndexPathForIdentifier:@2], [NSIndexPath indexPathForItem:1 inSection:0]); + XCTAssertEqualObjects([result oldIndexPathForIdentifier:@3], [NSIndexPath indexPathForItem:2 inSection:0]); + XCTAssertEqualObjects([result oldIndexPathForIdentifier:@4], [NSIndexPath indexPathForItem:3 inSection:0]); + XCTAssertEqualObjects([result oldIndexPathForIdentifier:@5], [NSIndexPath indexPathForItem:4 inSection:0]); + XCTAssertEqualObjects([result oldIndexPathForIdentifier:@6], [NSIndexPath indexPathForItem:5 inSection:0]); + XCTAssertEqualObjects([result oldIndexPathForIdentifier:@7], [NSIndexPath indexPathForItem:6 inSection:0]); + XCTAssertNil([result oldIndexPathForIdentifier:@8]); + XCTAssertNil([result oldIndexPathForIdentifier:@9]); +} + +- (void)test_whenDiffing_thatNewIndexPathsMatch { + NSArray *o = @[@1, @2, @3, @4, @5, @6, @7]; + NSArray *n = @[@2, @9, @3, @1, @5, @6, @8]; + IGListIndexPathResult *result = IGListDiffPaths(0, 1, o, n, IGListDiffEquality); + XCTAssertEqualObjects([result newIndexPathForIdentifier:@1], [NSIndexPath indexPathForItem:3 inSection:1]); + XCTAssertEqualObjects([result newIndexPathForIdentifier:@2], [NSIndexPath indexPathForItem:0 inSection:1]); + XCTAssertEqualObjects([result newIndexPathForIdentifier:@3], [NSIndexPath indexPathForItem:2 inSection:1]); + XCTAssertNil([result newIndexPathForIdentifier:@4]); + XCTAssertEqualObjects([result newIndexPathForIdentifier:@5], [NSIndexPath indexPathForItem:4 inSection:1]); + XCTAssertEqualObjects([result newIndexPathForIdentifier:@6], [NSIndexPath indexPathForItem:5 inSection:1]); + XCTAssertNil([result newIndexPathForIdentifier:@7]); + XCTAssertEqualObjects([result newIndexPathForIdentifier:@8], [NSIndexPath indexPathForItem:6 inSection:1]); + XCTAssertEqualObjects([result newIndexPathForIdentifier:@9], [NSIndexPath indexPathForItem:1 inSection:1]); +} + +@end diff --git a/Tests/IGListDisplayHandlerTests.m b/Tests/IGListDisplayHandlerTests.m new file mode 100644 index 000000000..231652523 --- /dev/null +++ b/Tests/IGListDisplayHandlerTests.m @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import + +#import "IGListAdapterInternal.h" +#import "IGListDisplayHandler.h" +#import "IGListTestAdapterDataSource.h" +#import "IGListTestSection.h" + +@interface IGListDisplayHandlerTests : XCTestCase + +@property (nonatomic, strong) IGListDisplayHandler *displayHandler; +@property (nonatomic, strong) IGListAdapter *adapter; +@property (nonatomic, strong) id mockDisplayDelegate; +@property (nonatomic, strong) id mockAdapterDelegate; +@property (nonatomic, strong) id mockAdapterDataSource; +@property (nonatomic, strong) IGListTestSection *list; +@property (nonatomic, strong) id object; + +@end + +@implementation IGListDisplayHandlerTests + +- (void)setUp { + [super setUp]; + + self.list = [[IGListTestSection alloc] init]; + self.object = [[NSObject alloc] init]; + self.displayHandler = [[IGListDisplayHandler alloc] init]; + IGListCollectionView *collectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + self.mockAdapterDataSource = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterDataSource)]; + IGListAdapterUpdater *updater = [IGListAdapterUpdater new]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:0]; + self.adapter.collectionView = collectionView; + self.adapter.dataSource = self.mockAdapterDataSource; + self.mockDisplayDelegate = [OCMockObject mockForProtocol:@protocol(IGListDisplayDelegate)]; + self.mockAdapterDelegate = [OCMockObject mockForProtocol:@protocol(IGListAdapterDelegate)]; +} + +- (void)tearDown { + [super tearDown]; + self.list.displayDelegate = nil; + self.adapter.delegate = nil; +} + +- (void)test_whenDisplayingFirstCell_thatDisplayHandlerReceivesEvent { + NSIndexPath *path = [NSIndexPath new]; + UICollectionViewCell *cell = [UICollectionViewCell new]; + + [[self.mockDisplayDelegate expect] listAdapter:self.adapter willDisplaySectionController:self.list]; + [[self.mockDisplayDelegate expect] listAdapter:self.adapter willDisplaySectionController:self.list cell:cell atIndex:path.item]; + + [[self.mockAdapterDelegate expect] listAdapter:self.adapter willDisplayObject:self.object atIndex:path.section]; + + self.list.displayDelegate = self.mockDisplayDelegate; + self.adapter.delegate = self.mockAdapterDelegate; + [self.displayHandler willDisplayCell:cell forListAdapter:self.adapter sectionController:self.list object:self.object indexPath:path]; + + [self.mockDisplayDelegate verify]; + [self.mockAdapterDelegate verify]; +} + +- (void)test_whenDisplayingSecondCell_thatDisplayHandlerReceivesEvent { + // simulate first cell appearing in the collection view + NSIndexPath *firstPath = [NSIndexPath indexPathForItem:0 inSection:0]; + [self.displayHandler willDisplayCell:[UICollectionViewCell new] forListAdapter:self.adapter sectionController:self.list object:self.object indexPath:firstPath]; + + NSIndexPath *nextPath = [NSIndexPath indexPathForItem:1 inSection:0]; + UICollectionViewCell *cell = [UICollectionViewCell new]; + [[self.mockDisplayDelegate expect] listAdapter:self.adapter willDisplaySectionController:self.list cell:cell atIndex:nextPath.item]; + [[self.mockDisplayDelegate reject] listAdapter:self.adapter willDisplaySectionController:self.list]; + + [[self.mockAdapterDelegate reject] listAdapter:self.adapter willDisplayObject:self.object atIndex:firstPath.section]; + + self.list.displayDelegate = self.mockDisplayDelegate; + self.adapter.delegate = self.mockAdapterDelegate; + [self.displayHandler willDisplayCell:cell forListAdapter:self.adapter sectionController:self.list object:self.object indexPath:nextPath]; + + [self.mockDisplayDelegate verify]; + [self.mockAdapterDelegate verify]; +} + +- (void)test_whenEndDisplayingSecondToLastCell_thatDisplayHandlerReceivesEvent { + // simulate first cell appearing in the collection view + NSIndexPath *firstPath = [NSIndexPath indexPathForItem:0 inSection:0]; + UICollectionViewCell *cellOne = [UICollectionViewCell new]; + UICollectionViewCell *cellTwo = [UICollectionViewCell new]; + + [self.displayHandler willDisplayCell:cellOne forListAdapter:self.adapter sectionController:self.list object:self.object indexPath:firstPath]; + + NSIndexPath *nextPath = [NSIndexPath indexPathForItem:1 inSection:0]; + + [self.displayHandler willDisplayCell:cellTwo forListAdapter:self.adapter sectionController:self.list object:self.object indexPath:nextPath]; + + [[self.mockDisplayDelegate reject] listAdapter:self.adapter didEndDisplayingSectionController:self.list]; + [[self.mockDisplayDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:self.list cell:cellOne atIndex:firstPath.item]; + + [[self.mockAdapterDelegate reject] listAdapter:self.adapter didEndDisplayingObject:self.object atIndex:firstPath.section]; + + self.list.displayDelegate = self.mockDisplayDelegate; + self.adapter.delegate = self.mockAdapterDelegate; + [self.displayHandler didEndDisplayingCell:cellOne forListAdapter:self.adapter sectionController:self.list indexPath:firstPath]; + + [self.mockDisplayDelegate verify]; + [self.mockAdapterDelegate verify]; +} + +- (void)test_whenEndDisplayingLastCell_thatDisplayHandlerReceivesEvent { + // simulate first cell appearing then disappearing in the collection view + NSIndexPath *firstPath = [NSIndexPath indexPathForItem:0 inSection:0]; + UICollectionViewCell *cell = [UICollectionViewCell new]; + + [self.displayHandler willDisplayCell:cell forListAdapter:self.adapter sectionController:self.list object:self.object indexPath:firstPath]; + + [[self.mockDisplayDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:self.list]; + [[self.mockDisplayDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:self.list cell:cell atIndex:firstPath.item]; + + [[self.mockAdapterDelegate expect] listAdapter:self.adapter didEndDisplayingObject:self.object atIndex:firstPath.section]; + + self.list.displayDelegate = self.mockDisplayDelegate; + self.adapter.delegate = self.mockAdapterDelegate; + [self.displayHandler didEndDisplayingCell:cell forListAdapter:self.adapter sectionController:self.list indexPath:firstPath]; + + [self.mockDisplayDelegate verify]; + [self.mockAdapterDelegate verify]; +} + +- (void)test_whenEndDisplayingCell_withCellNeverDisplayed_thatDisplayHandlerReceivesNoEvent { + //simulate a cell received didEndDisplay when it didn't receive willDisplay. OS 7 issue only + NSIndexPath *firstPath = [NSIndexPath indexPathForItem:0 inSection:0]; + UICollectionViewCell *cell = [UICollectionViewCell new]; + + // all following methods shouldn't be called. + [[self.mockDisplayDelegate reject] listAdapter:self.adapter didEndDisplayingSectionController:self.list]; + [[self.mockDisplayDelegate reject] listAdapter:self.adapter didEndDisplayingSectionController:self.list cell:cell atIndex:firstPath.item]; + [[self.mockAdapterDelegate reject] listAdapter:self.adapter didEndDisplayingObject:self.object atIndex:firstPath.section]; + + self.list.displayDelegate = self.mockDisplayDelegate; + self.adapter.delegate = self.mockAdapterDelegate; + [self.displayHandler didEndDisplayingCell:cell forListAdapter:self.adapter sectionController:self.list indexPath:firstPath]; +} + +- (void)test_whenEndDisplayingCell_withEndDisplayTwice_thatDisplayHandlerReceivesOneEvent { + //simulate a cell received didEndDisplay twice but willDisplay once. OS 7 issue only + NSIndexPath *firstPath = [NSIndexPath indexPathForItem:0 inSection:0]; + UICollectionViewCell *cell = [UICollectionViewCell new]; + + [self.displayHandler willDisplayCell:cell forListAdapter:self.adapter sectionController:self.list object:self.object indexPath:firstPath]; + + [[self.mockDisplayDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:self.list]; + [[self.mockDisplayDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:self.list cell:cell atIndex:firstPath.item]; + [[self.mockAdapterDelegate expect] listAdapter:self.adapter didEndDisplayingObject:self.object atIndex:firstPath.section]; + + [[self.mockDisplayDelegate reject] listAdapter:self.adapter didEndDisplayingSectionController:self.list]; + [[self.mockDisplayDelegate reject] listAdapter:self.adapter didEndDisplayingSectionController:self.list cell:cell atIndex:firstPath.item]; + [[self.mockAdapterDelegate reject] listAdapter:self.adapter didEndDisplayingObject:self.object atIndex:firstPath.section]; + + self.list.displayDelegate = self.mockDisplayDelegate; + self.adapter.delegate = self.mockAdapterDelegate; + //first call + [self.displayHandler didEndDisplayingCell:cell forListAdapter:self.adapter sectionController:self.list indexPath:firstPath]; + //second call + [self.displayHandler didEndDisplayingCell:cell forListAdapter:self.adapter sectionController:self.list indexPath:firstPath]; + + [self.mockDisplayDelegate verify]; + [self.mockAdapterDelegate verify]; +} + + +- (void)test_whenCellInserted_withDisplayedCellExistingAtPath_thatDisplayHandlerReceivesCorrectParams { + // simulate first cell appearing in the collection view + NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0]; + UICollectionViewCell *cellOne = [UICollectionViewCell new]; + + // display the "old" cell/object + [self.displayHandler willDisplayCell:cellOne forListAdapter:self.adapter sectionController:self.list object:self.object indexPath:path]; + + // simulate a new object being inserted into the index path of the old section + IGListTestSection *anotherList = [IGListTestSection new]; + id anotherObject = [NSObject new]; + UICollectionViewCell *anotherCell = [UICollectionViewCell new]; + + [[self.mockDisplayDelegate expect] listAdapter:self.adapter willDisplaySectionController:anotherList]; + [[self.mockDisplayDelegate expect] listAdapter:self.adapter willDisplaySectionController:anotherList cell:anotherCell atIndex:path.item]; + + anotherList.displayDelegate = self.mockDisplayDelegate; + [self.displayHandler willDisplayCell:anotherCell forListAdapter:self.adapter sectionController:anotherList object:anotherObject indexPath:path]; + + [self.mockDisplayDelegate verify]; +} + +@end diff --git a/Tests/IGListIndexResultTests.m b/Tests/IGListIndexResultTests.m new file mode 100644 index 000000000..e9728ff25 --- /dev/null +++ b/Tests/IGListIndexResultTests.m @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGListIndexPathResultInternal.h" +#import "IGListIndexSetResultInternal.h" +#import "IGListMoveIndexInternal.h" +#import "IGListMoveIndexPathInternal.h" + +@interface IGListResultTests : XCTestCase + +@end + +@implementation IGListResultTests + +static NSIndexSet *indexSetWithIndexes(NSArray *indexes) { + NSMutableIndexSet *indexset = [NSMutableIndexSet new]; + for (NSNumber *i in indexes) { + [indexset addIndex:i.integerValue]; + } + return indexset; +} + +static NSIndexPath *indexPath(NSUInteger item, NSUInteger section) { + return [NSIndexPath indexPathForItem:item inSection:section]; +} + +- (void)testIndexSetResultConvertingUpdatesAndMoves { + IGListIndexSetResult *initResult = [[IGListIndexSetResult alloc] initWithInserts:indexSetWithIndexes(@[@2, @3]) + deletes:indexSetWithIndexes(@[@0]) + updates:indexSetWithIndexes(@[@1, @4]) + moves:@[ + [[IGListMoveIndex alloc] initWithFrom:1 to:5], + [[IGListMoveIndex alloc] initWithFrom:6 to:7] + ] + oldIndexMap:[NSMapTable new] + newIndexMap:[NSMapTable new] + ]; + IGListIndexSetResult *converted = [initResult resultWithUpdatedMovesAsDeleteInserts]; + NSIndexSet *expectedDeletes = indexSetWithIndexes(@[@0, @1]); + NSIndexSet *expectedInserts = indexSetWithIndexes(@[@2, @3, @5]); + NSIndexSet *expectedUpdates = indexSetWithIndexes(@[@4]); + NSArray *expectedMoves = @[ + [[IGListMoveIndex alloc] initWithFrom:6 to:7] + ]; + XCTAssertEqualObjects(converted.deletes, expectedDeletes); + XCTAssertEqualObjects(converted.inserts, expectedInserts); + XCTAssertEqualObjects(converted.updates, expectedUpdates); + XCTAssertEqualObjects(converted.moves, expectedMoves); +} + +- (void)testIndexPathResultConvertingUpdatesAndMoves { + IGListIndexPathResult *initResult = [[IGListIndexPathResult alloc] initWithInserts:@[ + indexPath(0, 1), + indexPath(1, 2), + ] + deletes:@[ + indexPath(0, 0), + ] + updates:@[ + indexPath(1, 1), + indexPath(2, 4), + ] + moves:@[ + [[IGListMoveIndexPath alloc] initWithFrom:indexPath(1, 1) to:indexPath(5, 1)], + [[IGListMoveIndexPath alloc] initWithFrom:indexPath(6, 0) to:indexPath(7, 0)], + ] + oldIndexPathMap:[NSMapTable new] + newIndexPathMap:[NSMapTable new] + ]; + IGListIndexPathResult *converted = [initResult resultWithUpdatedMovesAsDeleteInserts]; + NSArray *expectedDeletes = @[ + indexPath(0, 0), + indexPath(1, 1), + ]; + NSArray *expectedInserts = @[ + indexPath(0, 1), + indexPath(1, 2), + indexPath(5, 1), + ]; + NSArray *expectedUpdates = @[ + indexPath(2, 4), + ]; + NSArray *expectedMoves = @[ + [[IGListMoveIndexPath alloc] initWithFrom:indexPath(6, 0) to:indexPath(7, 0)], + ]; + XCTAssertEqualObjects([converted.deletes sortedArrayUsingSelector:@selector(compare:)], [expectedDeletes sortedArrayUsingSelector:@selector(compare:)]); + XCTAssertEqualObjects([converted.inserts sortedArrayUsingSelector:@selector(compare:)], [expectedInserts sortedArrayUsingSelector:@selector(compare:)]); + XCTAssertEqualObjects([converted.updates sortedArrayUsingSelector:@selector(compare:)], [expectedUpdates sortedArrayUsingSelector:@selector(compare:)]); + XCTAssertEqualObjects([converted.moves sortedArrayUsingSelector:@selector(compare:)], [expectedMoves sortedArrayUsingSelector:@selector(compare:)]); +} + +@end diff --git a/Tests/IGListKitTests-Bridging-Header.h b/Tests/IGListKitTests-Bridging-Header.h new file mode 100644 index 000000000..732b8069d --- /dev/null +++ b/Tests/IGListKitTests-Bridging-Header.h @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import diff --git a/Tests/IGListObjectMapTests.m b/Tests/IGListObjectMapTests.m new file mode 100644 index 000000000..c19200bfc --- /dev/null +++ b/Tests/IGListObjectMapTests.m @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGListSectionMap.h" +#import "IGListTestSection.h" +#import "IGTestObject.h" + +#define genTestObject(k, v) [[IGTestObject alloc] initWithKey:k value:v] + +@interface IGListSectionMapTests : XCTestCase + +@end + +@implementation IGListSectionMapTests + +- (void)test_whenUpdatingItems_thatArraysAreEqual { + NSArray *objects = @[@0, @1, @2]; + NSArray *sectionControllers = @[@"a", @"b", @"c"]; + IGListSectionMap *map = [[IGListSectionMap alloc] initWithMapTable:[NSMapTable strongToStrongObjectsMapTable]]; + [map updateWithObjects:objects sectionControllers:sectionControllers]; + XCTAssertEqualObjects(objects, map.objects); +} + +- (void)test_whenUpdatingItems_thatSectionControllersAreMappedForSection { + NSArray *objects = @[@0, @1, @2]; + NSArray *sectionControllers = @[@"a", @"b", @"c"]; + IGListSectionMap *map = [[IGListSectionMap alloc] initWithMapTable:[NSMapTable strongToStrongObjectsMapTable]]; + [map updateWithObjects:objects sectionControllers:sectionControllers]; + XCTAssertEqualObjects([map sectionControllerForSection:1], sectionControllers[1]); +} + +- (void)test_whenUpdatingItems_thatSectionControllersAreMappedForItem { + NSArray *objects = @[@0, @1, @2]; + NSArray *sectionControllers = @[@"a", @"b", @"c"]; + IGListSectionMap *map = [[IGListSectionMap alloc] initWithMapTable:[NSMapTable strongToStrongObjectsMapTable]]; + [map updateWithObjects:objects sectionControllers:sectionControllers]; + XCTAssertEqual([map sectionControllerForObject:objects[1]], sectionControllers[1]); +} + +- (void)test_whenUpdatingItems_thatSectionsAreMappedForSectionController { + NSArray *objects = @[@0, @1, @2]; + NSArray *sectionControllers = @[@"a", @"b", @"c"]; + IGListSectionMap *map = [[IGListSectionMap alloc] initWithMapTable:[NSMapTable strongToStrongObjectsMapTable]]; + [map updateWithObjects:objects sectionControllers:sectionControllers]; + XCTAssertEqual([map sectionForSectionController:sectionControllers[1]], 1); +} + +- (void)test_whenUpdatingItems_withUnknownItem_thatSectionControllerIsNil { + NSArray *objects = @[@0, @1, @2]; + NSArray *sectionControllers = @[@"a", @"b", @"c"]; + IGListSectionMap *map = [[IGListSectionMap alloc] initWithMapTable:[NSMapTable strongToStrongObjectsMapTable]]; + [map updateWithObjects:objects sectionControllers:sectionControllers]; + XCTAssertNil([map sectionControllerForObject:@4]); +} + +- (void)test_whenUpdatingItems_withSectionController_thatSectionIsNotFound { + NSArray *objects = @[@0, @1, @2]; + NSArray *sectionControllers = @[[IGListTestSection new], [IGListTestSection new], [IGListTestSection new]]; + IGListSectionMap *map = [[IGListSectionMap alloc] initWithMapTable:[NSMapTable strongToStrongObjectsMapTable]]; + [map updateWithObjects:objects sectionControllers:sectionControllers]; + XCTAssertEqual([map sectionForSectionController:[IGListTestSection new]], NSNotFound); +} + +- (void)test_whenEnumeratingMap_withStopFlagSet_thatEnumerationEndsEarly { + NSArray *objects = @[@0, @1, @2]; + NSArray *sectionControllers = @[@"a", @"b", @"c"]; + IGListSectionMap *map = [[IGListSectionMap alloc] initWithMapTable:[NSMapTable strongToStrongObjectsMapTable]]; + [map updateWithObjects:objects sectionControllers:sectionControllers]; + __block NSInteger counter = 0; + [map enumerateUsingBlock:^(id item, IGListSectionController * sectionController, NSUInteger section, BOOL *stop) { + counter++; + *stop = section == 1; + }]; + XCTAssertEqual(counter, 2); +} + +@end diff --git a/Tests/IGListSingleItemControllerTests.m b/Tests/IGListSingleItemControllerTests.m new file mode 100644 index 000000000..5926b6531 --- /dev/null +++ b/Tests/IGListSingleItemControllerTests.m @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGTestCell.h" +#import "IGTestSingleItemDataSource.h" + +#define genTestObject(k, v) [[IGTestObject alloc] initWithKey:k value:v] + +#define genExpectation [self expectationWithDescription:NSStringFromSelector(_cmd)] + +@interface IGListSingleSectionControllerTests : XCTestCase + +@property (nonatomic, strong) IGListCollectionView *collectionView; +@property (nonatomic, strong) IGListAdapter *adapter; +@property (nonatomic, strong) IGListAdapterUpdater *updater; +@property (nonatomic, strong) IGTestSingleItemDataSource *dataSource; +@property (nonatomic, strong) UIWindow *window; + +@end + +@implementation IGListSingleSectionControllerTests + +- (void)setUp { + [super setUp]; + self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + self.collectionView = [[IGListCollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layout]; + [self.window addSubview:self.collectionView]; + self.dataSource = [[IGTestSingleItemDataSource alloc] init]; + self.updater = [[IGListAdapterUpdater alloc] init]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:self.updater viewController:nil workingRangeSize:2]; +} + +- (void)tearDown { + [super tearDown]; + self.window = nil; + self.collectionView = nil; + self.adapter = nil; + self.dataSource = nil; +} + +- (void)setupWithObjects:(NSArray *)objects { + self.dataSource.objects = objects; + self.adapter.collectionView = self.collectionView; + self.adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; +} + +- (void)test_whenDisplayingCollectionView_thatSectionsHaveOneItem { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 1); +} + +- (void)test_whenDisplayingCollectionView_thatCellsAreConfigured { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + IGTestCell *cell1 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + IGTestCell *cell2 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + IGTestCell *cell3 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]]; + XCTAssertEqualObjects(cell1.label.text, @"Foo"); + XCTAssertEqualObjects(cell2.label.text, @"Bar"); + XCTAssertEqualObjects(cell3.label.text, @"Baz"); +} + +- (void)test_whenDisplayingCollectionView_thatCellsAreSized { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + IGTestCell *cell1 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + IGTestCell *cell2 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + IGTestCell *cell3 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]]; + XCTAssertEqual(cell1.frame.size.height, 44); + XCTAssertEqual(cell2.frame.size.height, 44); + XCTAssertEqual(cell3.frame.size.height, 44); + XCTAssertEqual(cell1.frame.size.width, 100); + XCTAssertEqual(cell2.frame.size.width, 100); + XCTAssertEqual(cell3.frame.size.width, 100); +} + +- (void)test_whenItemUpdated_thatCellIsConfigured { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + self.dataSource.objects = @[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Qux"), // new value + genTestObject(@3, @"Baz"), + ]; + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + IGTestCell *cell2 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + XCTAssertEqualObjects(cell2.label.text, @"Qux"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +@end diff --git a/Tests/IGListStackItemControllerTests.m b/Tests/IGListStackItemControllerTests.m new file mode 100644 index 000000000..724613100 --- /dev/null +++ b/Tests/IGListStackItemControllerTests.m @@ -0,0 +1,434 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import +#import + +#import "IGListAdapterInternal.h" +#import "IGListDisplayHandler.h" +#import "IGListStackedSectionControllerInternal.h" +#import "IGListTestSection.h" +#import "IGTestStackedDataSource.h" + +static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; + +@interface IGListStackSectionControllerTests : XCTestCase + +@property (nonatomic, strong) UIWindow *window; +@property (nonatomic, strong) IGListCollectionView *collectionView; +@property (nonatomic, strong) IGListAdapter *adapter; +@property (nonatomic, strong) IGTestStackedDataSource *dataSource; + +@end + +@implementation IGListStackSectionControllerTests + +- (void)setUp { + [super setUp]; + + self.window = [[UIWindow alloc] initWithFrame:kStackTestFrame]; + + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + self.collectionView = [[IGListCollectionView alloc] initWithFrame:kStackTestFrame collectionViewLayout:layout]; + [self.window addSubview:self.collectionView]; + + self.dataSource = [[IGTestStackedDataSource alloc] init]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil workingRangeSize:0]; +} + +- (void)tearDown { + [super tearDown]; + + self.adapter = nil; + self.collectionView = nil; + self.dataSource = nil; +} + +- (void)setupWithObjects:(NSArray *)objects { + self.dataSource.objects = objects; + self.adapter.collectionView = self.collectionView; + self.adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; +} + +#pragma mark - Base + +- (void)test_whenInitializingStack_thatNumberOfItemsMatches { + IGListTestSection *section1 = [[IGListTestSection alloc] init]; + section1.items = 2; + IGListTestSection *section2 = [[IGListTestSection alloc] init]; + section2.items = 3; + IGListTestSection *section3 = [[IGListTestSection alloc] init]; + section3.items = 0; + IGListTestSection *section4 = [[IGListTestSection alloc] init]; + section4.items = 1; + + IGListStackedSectionController *stack = [[IGListStackedSectionController alloc] initWithSectionControllers:@[section1, section2, section3, section4]]; + + XCTAssertEqual([stack numberOfItems], 6); +} + +- (void)test_whenInitializingStack_thatSectionControllerIndexesMatch { + IGListTestSection *section1 = [[IGListTestSection alloc] init]; + section1.items = 2; + IGListTestSection *section2 = [[IGListTestSection alloc] init]; + section2.items = 3; + IGListTestSection *section3 = [[IGListTestSection alloc] init]; + section3.items = 0; + IGListTestSection *section4 = [[IGListTestSection alloc] init]; + section4.items = 1; + + IGListStackedSectionController *stack = [[IGListStackedSectionController alloc] initWithSectionControllers:@[section1, section2, section3, section4]]; + + XCTAssertEqualObjects([stack sectionControllerForObjectIndex:0], section1); + XCTAssertEqualObjects([stack sectionControllerForObjectIndex:1], section1); + XCTAssertEqualObjects([stack sectionControllerForObjectIndex:2], section2); + XCTAssertEqualObjects([stack sectionControllerForObjectIndex:3], section2); + XCTAssertEqualObjects([stack sectionControllerForObjectIndex:4], section2); + XCTAssertEqualObjects([stack sectionControllerForObjectIndex:5], section4); +} + +- (void)test_whenInitializingStack_thatSectionControllerOffsetsMatch { + IGListTestSection *section1 = [[IGListTestSection alloc] init]; + section1.items = 2; + IGListTestSection *section2 = [[IGListTestSection alloc] init]; + section2.items = 3; + IGListTestSection *section3 = [[IGListTestSection alloc] init]; + section3.items = 0; + IGListTestSection *section4 = [[IGListTestSection alloc] init]; + section4.items = 1; + + IGListStackedSectionController *stack = [[IGListStackedSectionController alloc] initWithSectionControllers:@[section1, section2, section3, section4]]; + XCTAssertEqual([stack offsetForSectionController:section1], 0); + XCTAssertEqual([stack offsetForSectionController:section2], 2); + XCTAssertEqual([stack offsetForSectionController:section3], 5); + XCTAssertEqual([stack offsetForSectionController:section4], 5); +} + + +#pragma mark - IGListCollectionContext + +- (void)test_whenReloadingStack_thatSectionControllerContainerMatchesCollectionView { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1]] + ]]; + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListTestSection *section1 = stack.sectionControllers[0]; + XCTAssertTrue(CGSizeEqualToSize([section1.collectionContext containerSize], kStackTestFrame.size)); +} + +- (void)test_whenQueryingCellIndex_thatIndexIsRelativeToSectionController { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @1, @2]] + ]]; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + + IGListTestSection *section1 = stack.sectionControllers[0]; + UICollectionViewCell *cell1 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + + IGListTestSection *section2 = stack.sectionControllers[1]; + UICollectionViewCell *cell2 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]; + + IGListTestSection *section3 = stack.sectionControllers[2]; + UICollectionViewCell *cell30 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:0]]; + UICollectionViewCell *cell31 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:3 inSection:0]]; + + // each controller in the stack has one cell, even though the item indexes are 0, 1, 2, 3 + XCTAssertEqual([section1.collectionContext indexForCell:cell1 sectionController:section1], 0); + XCTAssertEqual([section2.collectionContext indexForCell:cell2 sectionController:section2], 0); + XCTAssertEqual([section3.collectionContext indexForCell:cell30 sectionController:section3], 0); + XCTAssertEqual([section3.collectionContext indexForCell:cell31 sectionController:section3], 1); +} + +- (void)test_whenQueryingCells_thatCellIsRelativeToSectionController { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @1, @2]] + ]]; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + + IGListTestSection *section1 = stack.sectionControllers[0]; + UICollectionViewCell *cell1 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + + IGListTestSection *section2 = stack.sectionControllers[1]; + UICollectionViewCell *cell2 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]; + + IGListTestSection *section3 = stack.sectionControllers[2]; + UICollectionViewCell *cell30 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:0]]; + UICollectionViewCell *cell31 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:3 inSection:0]]; + + // each controller in the stack has one cell, even though the item indexes are 0, 1, 2, 3 + XCTAssertEqualObjects([section1.collectionContext cellForItemAtIndex:0 sectionController:section1], cell1); + XCTAssertEqualObjects([section2.collectionContext cellForItemAtIndex:0 sectionController:section2], cell2); + XCTAssertEqualObjects([section3.collectionContext cellForItemAtIndex:0 sectionController:section3], cell30); + XCTAssertEqualObjects([section3.collectionContext cellForItemAtIndex:1 sectionController:section3], cell31); +} + +- (void)test_whenQueryingSectionControllerSection_thatSectionMatchesStackSection { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @1]], + [[IGTestObject alloc] initWithKey:@1 value:@[@1, @1]] + ]]; + + IGListStackedSectionController *stack1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListStackedSectionController *stack2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + + IGListTestSection *section11 = stack1.sectionControllers[0]; + IGListTestSection *section12 = stack1.sectionControllers[1]; + IGListTestSection *section21 = stack2.sectionControllers[0]; + IGListTestSection *section22 = stack2.sectionControllers[1]; + + XCTAssertEqual([stack1.collectionContext sectionForSectionController:stack1], 0); + XCTAssertEqual([stack2.collectionContext sectionForSectionController:stack2], 1); + XCTAssertEqual([section11.collectionContext sectionForSectionController:section11], 0); + XCTAssertEqual([section12.collectionContext sectionForSectionController:section12], 0); + XCTAssertEqual([section21.collectionContext sectionForSectionController:section21], 1); + XCTAssertEqual([section22.collectionContext sectionForSectionController:section22], 1); +} + +- (void)test_whenReloadingItems_thatCollectionViewReloadsRelativeIndexPaths { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@2, @2]] + ]]; + + id mockCollectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + self.adapter.collectionView = mockCollectionView; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListTestSection *section2 = stack.sectionControllers[1]; + + [[mockCollectionView expect] reloadItemsAtIndexPaths:@[ + [NSIndexPath indexPathForItem:3 inSection:0] + ]]; + [section2.collectionContext reloadInSectionController:section2 atIndexes:[NSIndexSet indexSetWithIndex:1]]; + [mockCollectionView verify]; +} + +- (void)test_whenInsertingItems_thatCollectionViewReloadsRelativeIndexPaths { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@2, @2]] + ]]; + + id mockCollectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + self.adapter.collectionView = mockCollectionView; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListTestSection *section2 = stack.sectionControllers[1]; + section2.items = 3; + + [[mockCollectionView expect] insertItemsAtIndexPaths:@[ + [NSIndexPath indexPathForItem:4 inSection:0] + ]]; + [section2.collectionContext insertInSectionController:section2 atIndexes:[NSIndexSet indexSetWithIndex:2]]; + [mockCollectionView verify]; + + XCTAssertEqual([stack numberOfItems], 5); +} + +- (void)test_whenDeletingItems_thatCollectionViewReloadsRelativeIndexPaths { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@2, @2]] + ]]; + + id mockCollectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + self.adapter.collectionView = mockCollectionView; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListTestSection *section2 = stack.sectionControllers[1]; + section2.items = 1; + + [[mockCollectionView expect] deleteItemsAtIndexPaths:@[ + [NSIndexPath indexPathForItem:3 inSection:0] + ]]; + [section2.collectionContext deleteInSectionController:section2 atIndexes:[NSIndexSet indexSetWithIndex:1]]; + [mockCollectionView verify]; + + XCTAssertEqual([stack numberOfItems], 3); +} + +- (void)test_whenReloadingSectionController_thatCollectionViewReloadsStack { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@2, @2]] + ]]; + + id mockCollectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + self.adapter.collectionView = mockCollectionView; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListTestSection *section2 = stack.sectionControllers[1]; + section2.items = 3; + + // section 0 b/c any controller doing a full reload will queue reload of the entire stack + [[mockCollectionView expect] reloadSections:[NSIndexSet indexSetWithIndex:0]]; + [section2.collectionContext reloadSectionController:section2]; + [mockCollectionView verify]; + + XCTAssertEqual([stack numberOfItems], 5); +} + +- (void)test_whenDisplayingCell_thatEventsForwardedToSectionControllers { + id mock1Delegate = [OCMockObject mockForProtocol:@protocol(IGListDisplayDelegate)]; + id mock2Delegate = [OCMockObject mockForProtocol:@protocol(IGListDisplayDelegate)]; + IGListTestSection *section1 = [[IGListTestSection alloc] init]; + section1.items = 2; + section1.displayDelegate = mock1Delegate; + IGListTestSection *section2 = [[IGListTestSection alloc] init]; + section2.displayDelegate = mock2Delegate; + section2.items = 2; + UICollectionViewCell *cell1 = [UICollectionViewCell new]; + UICollectionViewCell *cell2 = [UICollectionViewCell new]; + + [[mock1Delegate expect] listAdapter:self.adapter willDisplaySectionController:section1]; + [[mock1Delegate expect] listAdapter:self.adapter willDisplaySectionController:section1 cell:cell1 atIndex:0]; + [[mock1Delegate expect] listAdapter:self.adapter willDisplaySectionController:section1 cell:cell2 atIndex:1]; + [[mock2Delegate reject] listAdapter:self.adapter willDisplaySectionController:section2]; + + IGListDisplayHandler *display = [[IGListDisplayHandler alloc] init]; + IGListStackedSectionController *stack = [[IGListStackedSectionController alloc] initWithSectionControllers:@[section1, section2]]; + + [display willDisplayCell:cell1 forListAdapter:self.adapter sectionController:stack object:@"a" indexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + [display willDisplayCell:cell2 forListAdapter:self.adapter sectionController:stack object:@"a" indexPath:[NSIndexPath indexPathForItem:1 inSection:0]]; + + [mock1Delegate verify]; + [mock2Delegate verify]; +} + +- (void)test_whenEndDisplayingCell_thatEventsForwardedToSectionControllers { + id mock1Delegate = [OCMockObject mockForProtocol:@protocol(IGListDisplayDelegate)]; + id mock2Delegate = [OCMockObject mockForProtocol:@protocol(IGListDisplayDelegate)]; + IGListTestSection *section1 = [[IGListTestSection alloc] init]; + section1.items = 2; + IGListTestSection *section2 = [[IGListTestSection alloc] init]; + section2.items = 2; + UICollectionViewCell *cell1 = [UICollectionViewCell new]; + UICollectionViewCell *cell2 = [UICollectionViewCell new]; + UICollectionViewCell *cell3 = [UICollectionViewCell new]; + UICollectionViewCell *cell4 = [UICollectionViewCell new]; + + IGListDisplayHandler *display = [[IGListDisplayHandler alloc] init]; + IGListStackedSectionController *stack = [[IGListStackedSectionController alloc] initWithSectionControllers:@[section1, section2]]; + + // display all 4 cells (2 per child section controller) + [display willDisplayCell:cell1 forListAdapter:self.adapter sectionController:stack object:@"a" indexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + [display willDisplayCell:cell2 forListAdapter:self.adapter sectionController:stack object:@"a" indexPath:[NSIndexPath indexPathForItem:1 inSection:0]]; + [display willDisplayCell:cell3 forListAdapter:self.adapter sectionController:stack object:@"a" indexPath:[NSIndexPath indexPathForItem:2 inSection:0]]; + [display willDisplayCell:cell4 forListAdapter:self.adapter sectionController:stack object:@"a" indexPath:[NSIndexPath indexPathForItem:3 inSection:0]]; + + section1.displayDelegate = mock1Delegate; + section2.displayDelegate = mock2Delegate; + + [[mock1Delegate expect] listAdapter:self.adapter didEndDisplayingSectionController:section1]; + [[mock1Delegate expect] listAdapter:self.adapter didEndDisplayingSectionController:section1 cell:cell1 atIndex:0]; + [[mock1Delegate expect] listAdapter:self.adapter didEndDisplayingSectionController:section1 cell:cell2 atIndex:1]; + [[mock2Delegate reject] listAdapter:self.adapter didEndDisplayingSectionController:section2]; + + [display didEndDisplayingCell:cell1 forListAdapter:self.adapter sectionController:stack indexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + [display didEndDisplayingCell:cell2 forListAdapter:self.adapter sectionController:stack indexPath:[NSIndexPath indexPathForItem:1 inSection:0]]; + + [mock1Delegate verify]; + [mock2Delegate verify]; +} + +- (void)test_whenRemovingCell_thatEventsForwardedToSectionControllers { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2]], + [[IGTestObject alloc] initWithKey:@1 value:@[@1, @2]] + ]]; + + IGTestObject *obj1 = self.dataSource.objects[0]; + IGTestObject *obj2 = self.dataSource.objects[1]; + + self.dataSource.objects = @[obj1]; + + id mockDelegate = [OCMockObject mockForProtocol:@protocol(IGListDisplayDelegate)]; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:obj2]; + + IGListTestSection *section1 = stack.sectionControllers[0]; + IGListTestSection *section2 = stack.sectionControllers[1]; + + section1.displayDelegate = mockDelegate; + section2.displayDelegate = mockDelegate; + + UICollectionViewCell *cell1 = [self.adapter.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + UICollectionViewCell *cell2 = [self.adapter.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:1]]; + UICollectionViewCell *cell3 = [self.adapter.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:1]]; + + [[mockDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:section1]; + [[mockDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:section2]; + [[mockDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:section1 cell:cell1 atIndex:0]; + [[mockDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:section2 cell:cell2 atIndex:0]; + [[mockDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:section2 cell:cell3 atIndex:1]; + + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self.adapter reloadDataWithCompletion:^(BOOL finished) { + [mockDelegate verify]; + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +- (void)test_whenQueryingVisibleSectionControllers_withCellsOffscreen_thatOnlyVisibleReturned { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@3, @4, @0, @5, @6]] + ]]; + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + + IGListTestSection *section1 = stack.sectionControllers[0]; + IGListTestSection *section2 = stack.sectionControllers[1]; + IGListTestSection *section3 = stack.sectionControllers[2]; + IGListTestSection *section4 = stack.sectionControllers[3]; + IGListTestSection *section5 = stack.sectionControllers[4]; + + XCTAssertEqual([self.adapter visibleCellsForSectionController:stack].count, 10); + XCTAssertEqual([stack visibleCellsForSectionController:section1].count, 3); + XCTAssertEqual([stack visibleCellsForSectionController:section2].count, 4); + XCTAssertEqual([stack visibleCellsForSectionController:section3].count, 0); + XCTAssertEqual([stack visibleCellsForSectionController:section4].count, 3); + XCTAssertEqual([stack visibleCellsForSectionController:section5].count, 0); +} + +- (void)test_whenPerformingItemUpdates_thatMutationsMapToSectionControllers { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@1 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @1]] + ]]; + + IGTestObject *object = self.dataSource.objects[1]; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:object]; + IGListTestSection *section1 = stack.sectionControllers[0]; + IGListTestSection *section2 = stack.sectionControllers[1]; + + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [section1.collectionContext performBatchAnimated:YES updates:^{ + section1.items = 3; + [section1.collectionContext insertInSectionController:section1 atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]]; + } completion:^(BOOL finished2) { + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 6); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 7); + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 2); + [expectation fulfill]; + }]; + + [section2.collectionContext performBatchAnimated:YES updates:^{ + section2.items = 1; + [section2.collectionContext deleteInSectionController:section2 atIndexes:[NSIndexSet indexSetWithIndex:0]]; + } completion:nil]; + + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +@end diff --git a/Tests/IGListWorkingRangeHandlerTests.m b/Tests/IGListWorkingRangeHandlerTests.m new file mode 100644 index 000000000..eddf77359 --- /dev/null +++ b/Tests/IGListWorkingRangeHandlerTests.m @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import +#import + +#import "IGListAdapterInternal.h" +#import "IGListTestAdapterDataSource.h" +#import "IGListTestSection.h" +#import "IGListWorkingRangeHandler.h" + +@interface _IGTestWorkingRangeAdapterDataSource : NSObject + +- (instancetype)initWithObjects:(NSArray *)objects + objectToControllerMap:(NSDictionary *)map; + +@end + +@implementation _IGTestWorkingRangeAdapterDataSource { + NSArray *_objects; + NSDictionary *_map; +} + +- (instancetype)initWithObjects:(NSArray *)objects + objectToControllerMap:(NSDictionary *)map { + if (self = [super init]) { + _objects = objects; + _map = map; + } + return self; +} + +- (UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { + return nil; +} + +- (NSArray> *)objectsForListAdapter:(IGListAdapter *)listAdapter { + return _objects; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter + sectionControllerForObject:(id)object { + return [_map objectForKey:object]; +} + +@end + +@interface IGListWorkingRangeHandlerTests : XCTestCase + +@end + +@implementation IGListWorkingRangeHandlerTests + +- (void)test_whenDisplayingItemAtPath_withWorkingRangeSizeZero_thatItemEntersWorkingRange { + // Arrange 1: Set up a simple collection view and adapter with a single element. + IGListTestSection *controller = [[IGListTestSection alloc] init]; + NSString *object = @"obj"; + _IGTestWorkingRangeAdapterDataSource *ds = [[_IGTestWorkingRangeAdapterDataSource alloc] initWithObjects:@[object] + objectToControllerMap:@{object: controller}]; + IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:0]; + id collectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + adapter.collectionView = collectionView; + id mockWorkingRangeDelegate = [OCMockObject mockForProtocol:@protocol(IGListWorkingRangeDelegate)]; + + adapter.dataSource = ds; + controller.workingRangeDelegate = mockWorkingRangeDelegate; + + // Arrange 2: Force an update so we get the objects we configured through the system. + [adapter performUpdatesAnimated:NO completion:nil]; + + // Act: Tell the working range handler that the first, and only item in the list will be displayed. + [[mockWorkingRangeDelegate expect] listAdapter:adapter sectionControllerWillEnterWorkingRange:controller]; + [adapter.workingRangeHandler willDisplayItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] forListAdapter:adapter]; + + [mockWorkingRangeDelegate verifyWithDelay:5]; +} + +- (void)test_whenDisplayingItemAtPath_withWorkingRangeSizeZero_thenHidingThatItem_thatItemLeavesWorkingRange { + // Arrange 1: Set up a simple collection view and adapter with a single element. + IGListTestSection *controller = [[IGListTestSection alloc] init]; + NSString *object = @"obj"; + _IGTestWorkingRangeAdapterDataSource *ds = [[_IGTestWorkingRangeAdapterDataSource alloc] initWithObjects:@[object] + objectToControllerMap:@{object: controller}]; + IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:0]; + id collectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + adapter.collectionView = collectionView; + id mockWorkingRangeDelegate = [OCMockObject mockForProtocol:@protocol(IGListWorkingRangeDelegate)]; + + adapter.dataSource = ds; + controller.workingRangeDelegate = mockWorkingRangeDelegate; + + // Arrange 2: Force an update so we get the objects we configured through the system. + [adapter performUpdatesAnimated:NO completion:nil]; + + // Arrange 3: Tell the working range handler that the first, and only item in the list will be displayed. + [[mockWorkingRangeDelegate expect] listAdapter:adapter sectionControllerWillEnterWorkingRange:controller]; + [adapter.workingRangeHandler willDisplayItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] forListAdapter:adapter]; + + // Arrange 4: Wait for the item to move in-range + [mockWorkingRangeDelegate verifyWithDelay:5]; + + // Act: Tell the working range handler that the first item is now hidden. + [[mockWorkingRangeDelegate expect] listAdapter:adapter sectionControllerDidExitWorkingRange:controller]; + [adapter.workingRangeHandler didEndDisplayingItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] forListAdapter:adapter]; + + [mockWorkingRangeDelegate verifyWithDelay:5]; +} + +- (void)test_whenDisplayingItemAtPath_withWorkingRangeSizeOne_thatNextItemEntersWorkingRange { + // Arrange 1: Set up a simple collection view and adapter with two elements. + IGListTestSection *controller1 = [[IGListTestSection alloc] init]; + NSString *object1 = @"obj1"; + IGListTestSection *controller2 = [[IGListTestSection alloc] init]; + NSString *object2 = @"obj2"; + _IGTestWorkingRangeAdapterDataSource *ds = [[_IGTestWorkingRangeAdapterDataSource alloc] initWithObjects:@[object1, object2] + objectToControllerMap:@{object1: controller1, + object2: controller2}]; + IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:1]; + id collectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + adapter.collectionView = collectionView; + id mockWorkingRangeDelegate = [OCMockObject mockForProtocol:@protocol(IGListWorkingRangeDelegate)]; + + adapter.dataSource = ds; + controller2.workingRangeDelegate = mockWorkingRangeDelegate; + + // Arrange 2: Force an update so we get the objects we configured through the system. + [adapter performUpdatesAnimated:NO completion:nil]; + + // Act: Tell the working range handler that the first, and only item in the list will be displayed. + [[mockWorkingRangeDelegate expect] listAdapter:adapter sectionControllerWillEnterWorkingRange:controller2]; + [adapter.workingRangeHandler willDisplayItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] forListAdapter:adapter]; + + [mockWorkingRangeDelegate verifyWithDelay:5]; +} + +- (void)test_whenDisplayingItemAtPath_withWorkingRangeSizeOne_thatThirdItemDoesNotEnterWorkingRange { + // Arrange 1: Set up a simple collection view and adapter with three elements. + IGListTestSection *controller1 = [[IGListTestSection alloc] init]; + NSString *object1 = @"obj1"; + IGListTestSection *controller2 = [[IGListTestSection alloc] init]; + NSString *object2 = @"obj2"; + IGListTestSection *controller3 = [[IGListTestSection alloc] init]; + NSString *object3 = @"obj3"; + _IGTestWorkingRangeAdapterDataSource *ds = [[_IGTestWorkingRangeAdapterDataSource alloc] initWithObjects:@[object1, object2, object3] + objectToControllerMap:@{object1: controller1, + object2: controller2, + object3: controller3}]; + IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:1]; + id collectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + adapter.collectionView = collectionView; + id mockWorkingRangeDelegate2 = [OCMockObject mockForProtocol:@protocol(IGListWorkingRangeDelegate)]; + id mockWorkingRangeDelegate3 = [OCMockObject mockForProtocol:@protocol(IGListWorkingRangeDelegate)]; + + adapter.dataSource = ds; + controller2.workingRangeDelegate = mockWorkingRangeDelegate2; + controller3.workingRangeDelegate = mockWorkingRangeDelegate3; + + // Arrange 2: Force an update so we get the objects we configured through the system. + [adapter performUpdatesAnimated:NO completion:nil]; + + // Act: Tell the working range handler that the first, and only item in the list will be displayed. + [[mockWorkingRangeDelegate2 expect] listAdapter:adapter sectionControllerWillEnterWorkingRange:controller2]; + [[mockWorkingRangeDelegate3 reject] listAdapter:[OCMArg any] sectionControllerWillEnterWorkingRange:[OCMArg any]]; + [adapter.workingRangeHandler willDisplayItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] forListAdapter:adapter]; + + [mockWorkingRangeDelegate2 verifyWithDelay:5]; + [mockWorkingRangeDelegate3 verify]; +} + +- (void)test_whenDisplayingItemAtPath_withWorkingRangeSizeOne_thenEndDisplayingThatItem_thatNextItemLeavesWorkingRange { + // Arrange 1: Set up a simple collection view and adapter with two elements. + IGListTestSection *controller1 = [[IGListTestSection alloc] init]; + NSString *object1 = @"obj1"; + IGListTestSection *controller2 = [[IGListTestSection alloc] init]; + NSString *object2 = @"obj2"; + _IGTestWorkingRangeAdapterDataSource *ds = [[_IGTestWorkingRangeAdapterDataSource alloc] initWithObjects:@[object1, object2] + objectToControllerMap:@{object1: controller1, + object2: controller2}]; + IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init]; + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:1]; + id collectionView = [OCMockObject niceMockForClass:[IGListCollectionView class]]; + adapter.collectionView = collectionView; + id mockWorkingRangeDelegate = [OCMockObject mockForProtocol:@protocol(IGListWorkingRangeDelegate)]; + + adapter.dataSource = ds; + controller2.workingRangeDelegate = mockWorkingRangeDelegate; + + // Arrange 2: Force an update so we get the objects we configured through the system. + [adapter performUpdatesAnimated:NO completion:nil]; + + // Arrange 3: Tell the working range handler that the first, and only item in the list will be displayed. + [[mockWorkingRangeDelegate expect] listAdapter:adapter sectionControllerWillEnterWorkingRange:controller2]; + [adapter.workingRangeHandler willDisplayItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] forListAdapter:adapter]; + + // Arrange 4: Wait for the item to move in-range. + [mockWorkingRangeDelegate verifyWithDelay:5]; + + // Act: Hide the first item, and watch for the second item to leave the working range. + [[mockWorkingRangeDelegate expect] listAdapter:adapter sectionControllerDidExitWorkingRange:controller2]; + [adapter.workingRangeHandler didEndDisplayingItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] forListAdapter:adapter]; + + [mockWorkingRangeDelegate verifyWithDelay:5]; +} + +@end diff --git a/Tests/Info.plist b/Tests/Info.plist new file mode 100644 index 000000000..6c6c23c43 --- /dev/null +++ b/Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Tests/Objects/IGListTestAdapterDataSource.h b/Tests/Objects/IGListTestAdapterDataSource.h new file mode 100644 index 000000000..e873e975e --- /dev/null +++ b/Tests/Objects/IGListTestAdapterDataSource.h @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@interface IGListTestAdapterDataSource : NSObject + +// array of numbers which is then passed to -[IGListTestSection setItems:] +@property (nonatomic, strong) NSArray *objects; + +@property (nonatomic, strong) UIView *backgroundView; + +@end diff --git a/Tests/Objects/IGListTestAdapterDataSource.m b/Tests/Objects/IGListTestAdapterDataSource.m new file mode 100644 index 000000000..190488713 --- /dev/null +++ b/Tests/Objects/IGListTestAdapterDataSource.m @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListTestAdapterDataSource.h" + +#import + +#import "IGListTestSection.h" + +@implementation IGListTestAdapterDataSource + +- (NSArray *)objectsForListAdapter:(IGListAdapter *)listAdapter { + return self.objects; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object { + IGListTestSection *list = [[IGListTestSection alloc] init]; + return list; +} + +- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { + return self.backgroundView; +} + +@end diff --git a/Tests/Objects/IGListTestOffsettingLayout.h b/Tests/Objects/IGListTestOffsettingLayout.h new file mode 100644 index 000000000..373d064ce --- /dev/null +++ b/Tests/Objects/IGListTestOffsettingLayout.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface IGListTestOffsettingLayout : UICollectionViewFlowLayout + +@end diff --git a/Tests/Objects/IGListTestOffsettingLayout.m b/Tests/Objects/IGListTestOffsettingLayout.m new file mode 100644 index 000000000..9c1eacd0f --- /dev/null +++ b/Tests/Objects/IGListTestOffsettingLayout.m @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListTestOffsettingLayout.h" + +@implementation IGListTestOffsettingLayout + +- (void)prepareLayout { + [super prepareLayout]; + self.collectionView.contentOffset = CGPointMake(0, 10); +} + +@end diff --git a/Tests/Objects/IGListTestSection.h b/Tests/Objects/IGListTestSection.h new file mode 100644 index 000000000..565435368 --- /dev/null +++ b/Tests/Objects/IGListTestSection.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import + +@interface IGListTestSection : IGListSectionController + +@property (nonatomic, assign) NSInteger items; + +@end diff --git a/Tests/Objects/IGListTestSection.m b/Tests/Objects/IGListTestSection.m new file mode 100644 index 000000000..2a8a36232 --- /dev/null +++ b/Tests/Objects/IGListTestSection.m @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListTestSection.h" + +@implementation IGListTestSection + +- (NSArray *)cellClasses { + return @[UICollectionViewCell.class]; +} + +- (NSInteger)numberOfItems { + return self.items; +} + +- (CGSize)sizeForItemAtIndex:(NSInteger)index { + return CGSizeMake(100, 10); +} + +- (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { + return [self.collectionContext dequeueReusableCellOfClass:UICollectionViewCell.class + forSectionController:self + atIndex:index]; +} + +- (void)didUpdateToObject:(id)object { + if ([object isKindOfClass:[NSNumber class]]) { + self.items = [object integerValue]; + } +} + +- (void)didSelectItemAtIndex:(NSInteger)index {} + +@end diff --git a/Tests/Objects/IGListTestUICollectionViewDataSource.h b/Tests/Objects/IGListTestUICollectionViewDataSource.h new file mode 100644 index 000000000..f705baba1 --- /dev/null +++ b/Tests/Objects/IGListTestUICollectionViewDataSource.h @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@interface IGSectionObject : NSObject + +@property (nonatomic, strong) NSArray *objects; + ++ (instancetype)sectionWithObjects:(NSArray *)objects; + +@end + +@interface IGListTestUICollectionViewDataSource : NSObject + +@property (nonatomic, strong) NSArray *sections; + +- (instancetype)initWithCollectionView:(UICollectionView *)collectionView; + +@end diff --git a/Tests/Objects/IGListTestUICollectionViewDataSource.m b/Tests/Objects/IGListTestUICollectionViewDataSource.m new file mode 100644 index 000000000..6773028be --- /dev/null +++ b/Tests/Objects/IGListTestUICollectionViewDataSource.m @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGListTestUICollectionViewDataSource.h" + +@implementation IGSectionObject + ++ (instancetype)sectionWithObjects:(NSArray *)objects { + IGSectionObject *object = [[IGSectionObject alloc] init]; + object.objects = objects; + return object; +} + + +#pragma mark - IGListDiffable + +- (id)diffIdentifier { + // this is for test purposes only. please dont do this. + return [NSString stringWithFormat:@"%zi", self.hash]; +} + +- (BOOL)isEqual:(id)object { + if (object == self) { + return YES; + } else if ([object isKindOfClass:IGSectionObject.class]) { + return (self.objects && [self.objects isEqualToArray:[object objects]]) + || (!self.objects && ![object objects]); + } else { + return NO; + } +} + +@end + +@implementation IGListTestUICollectionViewDataSource + +- (instancetype)initWithCollectionView:(UICollectionView *)collectionView { + if (self = [super init]) { + collectionView.dataSource = self; + [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"]; + } + return self; +} + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return self.sections.count; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return [[self.sections[section] objects] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; + return cell; +} + +@end diff --git a/Tests/Objects/IGTestCell.h b/Tests/Objects/IGTestCell.h new file mode 100644 index 000000000..422aa21ae --- /dev/null +++ b/Tests/Objects/IGTestCell.h @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface IGTestCell : UICollectionViewCell + +@property (nonatomic, weak) id delegate; +@property (nonatomic, strong) UILabel *label; + +@end diff --git a/Tests/Objects/IGTestCell.m b/Tests/Objects/IGTestCell.m new file mode 100644 index 000000000..1ab462ea8 --- /dev/null +++ b/Tests/Objects/IGTestCell.m @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestCell.h" + +@implementation IGTestCell + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + _label = [[UILabel alloc] init]; + [self.contentView addSubview:_label]; + } + return self; +} + +@end diff --git a/Tests/Objects/IGTestDelegateController.h b/Tests/Objects/IGTestDelegateController.h new file mode 100644 index 000000000..4c286efbd --- /dev/null +++ b/Tests/Objects/IGTestDelegateController.h @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@class IGTestObject; + +@interface IGTestDelegateController : IGListSectionController + +@property (nonatomic, strong, readonly) IGTestObject *item; + +@property (nonatomic, copy) void (^itemUpdateBlock)(); +@property (nonatomic, copy) void (^cellConfigureBlock)(IGTestDelegateController *); +@property (nonatomic, assign, readonly) NSUInteger updateCount; + +@property (nonatomic, assign) NSUInteger willDisplayCount; +@property (nonatomic, assign) NSUInteger didEndDisplayCount; +@property (nonatomic, strong) NSCountedSet *willDisplayCellIndexes; +@property (nonatomic, strong) NSCountedSet *didEndDisplayCellIndexes; + +@end diff --git a/Tests/Objects/IGTestDelegateController.m b/Tests/Objects/IGTestDelegateController.m new file mode 100644 index 000000000..e79c7466e --- /dev/null +++ b/Tests/Objects/IGTestDelegateController.m @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestDelegateController.h" + +#import "IGTestCell.h" +#import "IGTestObject.h" + +@implementation IGTestDelegateController + +- (instancetype)init { + if (self = [super init]) { + _willDisplayCellIndexes = [NSCountedSet new]; + _didEndDisplayCellIndexes = [NSCountedSet new]; + } + return self; +} + +- (NSInteger)numberOfItems { + if ([self.item.value isKindOfClass:[NSNumber class]]) { + return [self.item.value integerValue]; + } + return 1; +} + +- (CGSize)sizeForItemAtIndex:(NSInteger)index { + return CGSizeMake(self.collectionContext.containerSize.width, 10); +} + +- (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { + IGTestCell *cell = [self.collectionContext dequeueReusableCellOfClass:IGTestCell.class + forSectionController:self atIndex:index]; + [[cell label] setText:[NSString stringWithFormat:@"%@", self.item.value]]; + [cell setDelegate:self]; + if (self.cellConfigureBlock) { + self.cellConfigureBlock(self); + } + return cell; +} + +- (void)didUpdateToObject:(id)object { + _updateCount++; + _item = object; + if (self.itemUpdateBlock) { + self.itemUpdateBlock(); + } +} + +- (id)displayDelegate { + return self; +} + +- (void)didSelectItemAtIndex:(NSInteger)index {} + +#pragma mark - IGListDisplayDelegate + +- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController { + self.willDisplayCount++; +} + +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController { + self.didEndDisplayCount++; +} + +- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController + cell:(UICollectionViewCell *)cell + atIndex:(NSInteger)index { + [self.willDisplayCellIndexes addObject:@(index)]; +} +- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController + cell:(UICollectionViewCell *)cell + atIndex:(NSInteger)index { + [self.didEndDisplayCellIndexes addObject:@(index)]; +} + +- (void)listAdapter:(IGListAdapter *)listAdapter didScrollSectionController:(IGListSectionController *)sectionController {} + +@end diff --git a/Tests/Objects/IGTestDelegateDataSource.h b/Tests/Objects/IGTestDelegateDataSource.h new file mode 100644 index 000000000..02a4b425e --- /dev/null +++ b/Tests/Objects/IGTestDelegateDataSource.h @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@class IGTestObject; +@class IGTestDelegateController; + +@interface IGTestDelegateDataSource : NSObject + +@property (nonatomic, strong) NSArray *objects; + +@property (nonatomic, copy) void (^cellConfigureBlock)(IGTestDelegateController *); + +@end diff --git a/Tests/Objects/IGTestDelegateDataSource.m b/Tests/Objects/IGTestDelegateDataSource.m new file mode 100644 index 000000000..1781eace8 --- /dev/null +++ b/Tests/Objects/IGTestDelegateDataSource.m @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestDelegateDataSource.h" + +#import + +#import "IGTestDelegateController.h" +#import "IGTestObject.h" + +@implementation IGTestDelegateDataSource + +- (NSArray *)objectsForListAdapter:(IGListAdapter *)listAdapter { + return self.objects; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object { + IGTestDelegateController *sectionController = [[IGTestDelegateController alloc] init]; + sectionController.cellConfigureBlock = self.cellConfigureBlock; + return sectionController; +} + +- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { + return nil; +} + +@end diff --git a/Tests/Objects/IGTestObject.h b/Tests/Objects/IGTestObject.h new file mode 100644 index 000000000..5b26808a9 --- /dev/null +++ b/Tests/Objects/IGTestObject.h @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@interface IGTestObject : NSObject + +- (instancetype)initWithKey:(id )key value:(id)value; + +@property (nonatomic, strong, readonly) id key; +@property (nonatomic, strong) id value; + +@end diff --git a/Tests/Objects/IGTestObject.m b/Tests/Objects/IGTestObject.m new file mode 100644 index 000000000..9c11f46be --- /dev/null +++ b/Tests/Objects/IGTestObject.m @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestObject.h" + +@implementation IGTestObject + +- (instancetype)initWithKey:(id)key value:(id)value { + if (self = [super init]) { + _key = [key copy]; + _value = value; + } + return self; +} + +- (instancetype)copyWithZone:(NSZone *)zone { + return [[IGTestObject alloc] initWithKey:self.key value:self.value]; +} + + +#pragma mark - IGListDiffable + +- (NSUInteger)hash { + return [self.key hash]; +} + +- (id)diffIdentifier { + return self.key; +} + +- (BOOL)isEqual:(id)object { + if (object == self) { + return YES; + } + if ([object isKindOfClass:[IGTestObject class]]) { + id k1 = self.key; + id k2 = [object key]; + id v1 = self.value; + id v2 = [(IGTestObject *)object value]; + return (v1 == v2 || [v1 isEqual:v2]) && (k1 == k2 || [k1 isEqual:k2]); + } + return NO; +} + +@end diff --git a/Tests/Objects/IGTestSingleItemDataSource.h b/Tests/Objects/IGTestSingleItemDataSource.h new file mode 100644 index 000000000..84e7cf9c3 --- /dev/null +++ b/Tests/Objects/IGTestSingleItemDataSource.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +#import "IGTestObject.h" + +@interface IGTestSingleItemDataSource : NSObject + +@property (nonatomic, strong) NSArray *objects; + +@end diff --git a/Tests/Objects/IGTestSingleItemDataSource.m b/Tests/Objects/IGTestSingleItemDataSource.m new file mode 100644 index 000000000..be1eb3455 --- /dev/null +++ b/Tests/Objects/IGTestSingleItemDataSource.m @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestSingleItemDataSource.h" + +#import + +#import "IGTestCell.h" + +@implementation IGTestSingleItemDataSource + +- (NSArray *)objectsForListAdapter:(IGListAdapter *)listAdapter { + return self.objects; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object { + void (^configureBlock)(id, __kindof UICollectionViewCell *) = ^(IGTestObject *item, IGTestCell *cell) { + cell.label.text = [item.value description]; + }; + CGSize (^sizeBlock)(id) = ^CGSize(id collectionContext) { + return CGSizeMake([collectionContext containerSize].width, 44); + }; + return [[IGListSingleSectionController alloc] initWithCellClass:IGTestCell.class + configureBlock:configureBlock + sizeBlock:sizeBlock]; +} + +- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { + return nil; +} + +@end diff --git a/Tests/Objects/IGTestStackedDataSource.h b/Tests/Objects/IGTestStackedDataSource.h new file mode 100644 index 000000000..b3a4ccb66 --- /dev/null +++ b/Tests/Objects/IGTestStackedDataSource.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +#import "IGTestObject.h" + +@interface IGTestStackedDataSource : NSObject + +@property (nonatomic, strong) NSArray *objects; + +@end diff --git a/Tests/Objects/IGTestStackedDataSource.m b/Tests/Objects/IGTestStackedDataSource.m new file mode 100644 index 000000000..99f916246 --- /dev/null +++ b/Tests/Objects/IGTestStackedDataSource.m @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestStackedDataSource.h" + +#import + +#import "IGListTestSection.h" + +@implementation IGTestStackedDataSource + +- (NSArray *)objectsForListAdapter:(IGListAdapter *)listAdapter { + return self.objects; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object { + NSMutableArray *controllers = [[NSMutableArray alloc] init]; + for (NSNumber *num in [(IGTestObject *)object value]) { + IGListTestSection *controller = [[IGListTestSection alloc] init]; + controller.items = [num integerValue]; + [controllers addObject:controller]; + } + return [[IGListStackedSectionController alloc] initWithSectionControllers:controllers]; +} + +- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { + return nil; +} + +@end diff --git a/Tests/Objects/IGTestSupplementarySource.h b/Tests/Objects/IGTestSupplementarySource.h new file mode 100644 index 000000000..68ed602e0 --- /dev/null +++ b/Tests/Objects/IGTestSupplementarySource.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@interface IGTestSupplementarySource : NSObject + +@property (nonatomic, strong, readwrite) NSArray *supportedElementKinds; + +@property (nonatomic, weak) id collectionContext; + +@property (nonatomic, weak) IGListSectionController *sectionController; + +@end diff --git a/Tests/Objects/IGTestSupplementarySource.m b/Tests/Objects/IGTestSupplementarySource.m new file mode 100644 index 000000000..fca3d0e2a --- /dev/null +++ b/Tests/Objects/IGTestSupplementarySource.m @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestSupplementarySource.h" + +@implementation IGTestSupplementarySource + +#pragma mark - IGListSupplementaryViewSource + +- (UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind + atIndex:(NSInteger)index { + return [self.collectionContext dequeueReusableSupplementaryViewOfKind:elementKind + forSectionController:self.sectionController + class:[UICollectionReusableView class] + atIndex:index]; +} + +- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index { + return CGSizeMake([self.collectionContext containerSize].width, 10); +} + +- (CGSize)estimatedSizeForSupplementaryViewOfKind:(NSString *)elementKind + atIndex:(NSInteger)index { + return CGSizeZero; +} + +@end diff --git a/build_docs.sh b/build_docs.sh new file mode 100755 index 000000000..dd8442b13 --- /dev/null +++ b/build_docs.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Docs by jazzy +# https://github.com/realm/jazzy +# ------------------------------ + +jazzy \ + --objc \ + --clean \ + --author 'Instagram' \ + --author_url 'https://twitter.com/fbOpenSource' \ + --github_url 'https://github.com/Instagram/IGListKit' \ + --sdk iphonesimulator \ + --module 'IGListKit' \ + --framework-root . \ + --umbrella-header Source/IGListKit.h \ + --readme README.md \ + --output docs/ diff --git a/docs/Categories.html b/docs/Categories.html new file mode 100644 index 000000000..83d6b98c2 --- /dev/null +++ b/docs/Categories.html @@ -0,0 +1,240 @@ + + + + Categories Reference + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

Categories

+

The following categories are available globally.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    This category adds diffing comparisons similar to adding the object into an NSSet, where the object’s isEqual: method +drives the uniqueness of the object.

    + +

    For instance, an NSString’s isEqual: will compare the value of the strings. So if you were to diff @cat and @cat +each object would have the same diff identifier.

    + +

    However objects that don’t implement a custom isEqual: (e.g. the NSObject base class), the diff will default to simple +pointer comparisons to establish uniqueness.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface NSObject (IGListDiffable)
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes.html b/docs/Classes.html new file mode 100644 index 000000000..c5eee835d --- /dev/null +++ b/docs/Classes.html @@ -0,0 +1,643 @@ + + + + Classes Reference + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

Classes

+

The following classes are available globally.

+ +
+
+
+
    +
  • +
    + + + + IGListAdapter + +
    +
    +
    +
    +
    +
    +

    IGListAdapter objects provide an abstraction for feeds of objects in a UICollectionView by breaking each object into +individual sections, called section controllers. These controllers (objects conforming to IGListSectionType) act as a +data source and delegate for each section.

    + +

    Feed implementations must act as the data source for an IGListAdapter in order to drive the objects and section +controllers in a collection view.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListAdapter : NSObject
    + +
    +
    +

    Swift

    +
    class IGListAdapter
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListAdapterUpdater + +
    +
    +
    +
    +
    +
    +

    This is an out-of-box upater for IGListAdapters. It conforms to IGListUpdatingDelegate and does re-entrant, coalesced +updating on a UICollectionView.

    + +

    It also uses IGDiffKit (a least-minimal diff) for calculating UI updates when IGListAdapter calls +-performUpdateWithCollectionView:fromObjects:toObjects:completion:.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListAdapterUpdater : NSObject <IGListUpdatingDelegate>
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListBatchUpdateData + +
    +
    +
    +
    +
    +
    +

    This object takes section indexes and item index paths and performs cleanup on init in order to perform a crash-free +update via -[UICollectionView performBatchUpdates:completion:].

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListBatchUpdateData : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListCollectionView + +
    +
    +
    +
    +
    +
    +

    This class is never actually used by the IGListKit infrastructure. It exists only to give compiler errors when editing +methods are called on the collection view returned by -[IGListAdapter collectionView].

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +

    Swift

    +
    class IGListCollectionView
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListIndexPathResult + +
    +
    +
    +
    +
    +
    +

    Result object returned when diffing with sections.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListIndexPathResult : NSObject
    + +
    +
    +

    Swift

    +
    class IGListIndexPathResult : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListIndexSetResult + +
    +
    +
    +
    +
    +
    +

    Result object returned when diffing with indexes.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListIndexSetResult : NSObject
    + +
    +
    +

    Swift

    +
    class IGListIndexSetResult : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListMoveIndex + +
    +
    +
    +
    +
    +
    +

    An object representing a move between indexes.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListMoveIndex : NSObject
    + +
    +
    +

    Swift

    +
    class IGListMoveIndex : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListMoveIndexPath + +
    +
    +
    +
    +
    +
    +

    An object representing a move between indexes.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListMoveIndexPath : NSObject
    + +
    +
    +

    Swift

    +
    class IGListMoveIndexPath : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
    +
    +
    +
    +

    The base class for section controllers used in the list infra. This class is meant to be subclassed.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListSectionController : NSObject
    + +
    +
    +

    Swift

    +
    class IGListSectionController
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    This section controller is meant to make building simple, single-cell feeds easier. By providing the type of cell, a block +to configure the cell, and a block to return the size of a cell, you can use an IGListAdapter-powered feed without +overcomplicating your architecture.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListSingleSectionController
    +    : IGListSectionController <IGListSectionType>
    + +
    +
    +

    Swift

    +
    class IGListSingleSectionController
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    This is a clustered section controller, composed of many child section controllers. It constructs and routes item-level +indexes to the appropriate child section controller with a local index. This lets you build section controllers made up +of individual units that can be shared and reused with other section controllers.

    + +

    For example, you can create a Comments section controller that displays lists of text that is used alongside photo, +video, or slideshow section controllers. You then have four small and manageable section controllers instead of one +huge class.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListStackedSectionController
    +    : IGListSectionController <IGListSectionType>
    + +
    +
    +

    Swift

    +
    class IGListStackedSectionController
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListAdapter.html b/docs/Classes/IGListAdapter.html new file mode 100644 index 000000000..12dca618e --- /dev/null +++ b/docs/Classes/IGListAdapter.html @@ -0,0 +1,1283 @@ + + + + IGListAdapter Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListAdapter

+
+
+
@interface IGListAdapter : NSObject
+ +
+
+

IGListAdapter objects provide an abstraction for feeds of objects in a UICollectionView by breaking each object into +individual sections, called section controllers. These controllers (objects conforming to IGListSectionType) act as a +data source and delegate for each section.

+ +

Feed implementations must act as the data source for an IGListAdapter in order to drive the objects and section +controllers in a collection view.

+ +
+
+
+
    +
  • +
    + + + + viewController + +
    +
    +
    +
    +
    +
    +

    The view controller that houses the adapter.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) UIViewController *viewController;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + collectionView + +
    +
    +
    +
    +
    +
    +

    The collection view used with the adapter.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) IGListCollectionView *collectionView;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + dataSource + +
    +
    +
    +
    +
    +
    +

    The object that acts as the data source for the list adapter.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) id<IGListAdapterDataSource>
    +    dataSource;
    + +
    +
    +

    Swift

    +
    weak var dataSource: AnyObject? { get set }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + delegate + +
    +
    +
    +
    +
    +
    +

    The object that receives top-level events for section controllers.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) id<IGListAdapterDelegate> delegate;
    + +
    +
    +

    Swift

    +
    weak var delegate: AnyObject? { get set }
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    The object that receives UICollectionViewDelegate events.

    + +

    @discussion This object /will not/ receive UIScrollViewDelegate events. Instead use scrollViewDelegate.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) id<UICollectionViewDelegate>
    +    collectionViewDelegate;
    + +
    +
    +

    Swift

    +
    weak var collectionViewDelegate: AnyObject? { get set }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + scrollViewDelegate + +
    +
    +
    +
    +
    +
    +

    The object that receives UIScrollViewDelegate events.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) id<UIScrollViewDelegate>
    +    scrollViewDelegate;
    + +
    +
    +

    Swift

    +
    weak var scrollViewDelegate: AnyObject? { get set }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + experiments + +
    +
    +
    +
    +
    +
    +

    A bitmask of experiments to conduct on the adapter.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (assign, readwrite, nonatomic) IGListExperiment experiments;
    + +
    +
    +

    Swift

    +
    var experiments: Int32 { get set }
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Initialize a new IGListAdapter object with a collection view, data source, and updating delegate.

    + +

    @discussion The working range is the number of objects beyond the visible objects (plus and minus) that should be +notified when they are close to being visible. For instance, if you have 3 objects on screen and a working range of 2, +the previous and succeeding 2 objects will be notified that they are within the working range. As you scroll the list +the range is updated as objects enter and exit the working range.

    + +

    To opt out of using the working range, you can provide a value of 0.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull instancetype)
    + initWithUpdater:(nonnull id<IGListUpdatingDelegate>)updatingDelegate
    +  viewController:(nullable UIViewController *)viewController
    +workingRangeSize:(NSUInteger)workingRangeSize;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + updatingDelegate + + +
    +

    An object that manages updates to the UICollectionView.

    + +
    +
    + + viewController + + +
    +

    The view controller that will house the adapter.

    + +
    +
    + + workingRangeSize + + +
    +

    The number of objects before and after the viewport to consider within the working range.

    + +
    +
    +
    +
    +

    Return Value

    +

    A new IGListAdapter object.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Perform an update from the previous state of the data source. This is analagous to calling +-[UICollectionView performBatchUpdates:completion:].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)performUpdatesAnimated:(BOOL)animated
    +                    completion:(nullable IGListUpdaterCompletion)completion;
    + +
    +
    +

    Swift

    +
    func performUpdatesAnimated(_ animated: Any!, completion: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + animated + + +
    +

    A flag indicating if the transition should be animated.

    + +
    +
    + + completion + + +
    +

    A block executed when the update completes.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Perform an immediate reload of the data in the data source, discarding the old objectss.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion;
    + +
    +
    +

    Swift

    +
    func reloadData(withCompletion completion: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + completion + + +
    +

    A block executed when the reload completes.

    + +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + -reloadObjects: + +
    +
    +
    +
    +
    +
    +

    Reload the infra for specific objectss only.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)reloadObjects:(nonnull NSArray *)objects;
    + +
    +
    +

    Swift

    +
    func reloadObjects(_ objects: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + objects + + +
    +

    The objects to reload.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Query the section index of a list. Constant time lookup.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (NSUInteger)sectionForSectionController:
    +    (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +

    Swift

    +
    func section(for sectionController: IGListSectionController!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + sectionController + + +
    +

    A list object.

    + +
    +
    +
    +
    +

    Return Value

    +

    The section index of the list or NSNotFound.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Fetch an section controller for an object in the feed. Constant time lookup.

    + +
    +

    See

    +

    -[IGListAdapterDataSource listAdapter:sectionControllerForObject:]

    + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (__kindof IGListSectionController<IGListSectionType> *_Nullable)
    +sectionControllerForObject:(nonnull id)object;
    + +
    +
    +

    Swift

    +
    func sectionController(for object: Any!) -> IGListSectionController?
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + object + + +
    +

    An object from the data source.

    + +
    +
    +
    +
    +

    Return Value

    +

    An section controller or nil.

    + +
    +
    +
    +
  • +
  • +
    + + + + -objectAtSection: + +
    +
    +
    +
    +
    +
    +

    Fetch the object corresponding to a section in the feed. Constant time lookup.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nullable id)objectAtSection:(NSUInteger)section;
    + +
    +
    +

    Swift

    +
    func object(atSection section: Any!) -> Any?
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + section + + +
    +

    A section in the feed.

    + +
    +
    +
    +
    +

    Return Value

    +

    An object or nil.

    + +
    +
    +
    +
  • +
  • +
    + + + + -sectionForObject: + +
    +
    +
    +
    +
    +
    +

    Fetch the section corresponding to an object in the feed. Constant time lookup.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (NSUInteger)sectionForObject:(nonnull id)object;
    + +
    +
    +

    Swift

    +
    func section(for object: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + object + + +
    +

    An object in the feed

    + +
    +
    +
    +
    +

    Return Value

    +

    A section index if found or NSNotFound.

    + +
    +
    +
    +
  • +
  • +
    + + + + -objects + +
    +
    +
    +
    +
    +
    +

    A copy of all the objects currently powering the adapter.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull NSArray *)objects;
    + +
    +
    +

    Swift

    +
    func objects() -> Any!
    + +
    +
    +
    +

    Return Value

    +

    An array of objects.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    An unordered array of the currently visible section controllers.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull NSArray<IGListSectionController<IGListSectionType> *> *)
    +    visibleSectionControllers;
    + +
    +
    +

    Swift

    +
    func visibleSectionControllers() -> Any!
    + +
    +
    +
    +

    Return Value

    +

    An array of section controllers.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Scroll to an object in the list adapter.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)scrollToObject:(nonnull id)object
    +    supplementaryKinds:(nullable NSArray<NSString *> *)supplementaryKinds
    +       scrollDirection:(UICollectionViewScrollDirection)scrollDirection
    +              animated:(BOOL)animated;
    + +
    +
    +

    Swift

    +
    func scroll(to object: Any!, supplementaryKinds: Any!, scrollDirection: Any!, animated: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + +
    + + object + + +
    +

    The object to scroll to.

    + +
    +
    + + supplementaryKinds + + +
    +

    The types of supplementary views in the section.

    + +
    +
    + + scrollDirection + + +
    +

    A flag indicating the direction to scroll.

    + +
    +
    + + animated + + +
    +

    A flag indicating if the transition should be animated.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Query the size of a cell at the specified index path.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (CGSize)sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath;
    + +
    +
    +

    Swift

    +
    func sizeForItem(atIndexPath indexPath: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + indexPath + + +
    +

    The index path of the cell.

    + +
    +
    +
    +
    +

    Return Value

    +

    The size of the cell.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Query the size of a supplementary view in the list at the specified index path.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (CGSize)sizeForSupplementaryViewOfKind:(nonnull NSString *)elementKind
    +                             atIndexPath:(nonnull NSIndexPath *)indexPath;
    + +
    +
    +

    Swift

    +
    func sizeForSupplementaryView(ofKind elementKind: Any!, atIndexPath indexPath: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + elementKind + + +
    +

    The kind of supplementary view.

    + +
    +
    + + indexPath + + +
    +

    The index path of the supplementary view.

    + +
    +
    +
    +
    +

    Return Value

    +

    The size of the supplementary view.

    + +
    +
    +
    +
  • +
  • +
    + + + + -init + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListAdapter : NSObject
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + +new + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListAdapter : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListAdapterUpdater.html b/docs/Classes/IGListAdapterUpdater.html new file mode 100644 index 000000000..01c082fcb --- /dev/null +++ b/docs/Classes/IGListAdapterUpdater.html @@ -0,0 +1,299 @@ + + + + IGListAdapterUpdater Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListAdapterUpdater

+
+
+
@interface IGListAdapterUpdater : NSObject <IGListUpdatingDelegate>
+ +
+
+

This is an out-of-box upater for IGListAdapters. It conforms to IGListUpdatingDelegate and does re-entrant, coalesced +updating on a UICollectionView.

+ +

It also uses IGDiffKit (a least-minimal diff) for calculating UI updates when IGListAdapter calls +-performUpdateWithCollectionView:fromObjects:toObjects:completion:.

+ +
+
+
+
    +
  • +
    + + + + delegate + +
    +
    +
    +
    +
    +
    +

    A delegate that receives events with data on the performance of a transition.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic)
    +    id<IGListAdapterUpdaterDelegate> _Nullable delegate;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + movesAsDeletesInserts + +
    +
    +
    +
    +
    +
    +

    A flag indicating if a move should be treated as a delete+insert.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (assign, readwrite, nonatomic) BOOL movesAsDeletesInserts;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + experiments + +
    +
    +
    +
    +
    +
    +

    A bitmask of experiments to conduct on the updater.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (assign, readwrite, nonatomic) IGListExperiment experiments;
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListBatchUpdateData.html b/docs/Classes/IGListBatchUpdateData.html new file mode 100644 index 000000000..a97355713 --- /dev/null +++ b/docs/Classes/IGListBatchUpdateData.html @@ -0,0 +1,557 @@ + + + + IGListBatchUpdateData Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListBatchUpdateData

+
+
+
@interface IGListBatchUpdateData : NSObject
+ +
+
+

This object takes section indexes and item index paths and performs cleanup on init in order to perform a crash-free +update via -[UICollectionView performBatchUpdates:completion:].

+ +
+
+
+
    +
  • +
    + + + + insertSections + +
    +
    +
    +
    +
    +
    +

    Clean section insert indexes.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic) NSIndexSet *_Nonnull insertSections;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + deleteSections + +
    +
    +
    +
    +
    +
    +

    Clean section delete indexes.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic) NSIndexSet *_Nonnull deleteSections;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + moveSections + +
    +
    +
    +
    +
    +
    +

    Clean section moves.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic)
    +    NSSet<IGListMoveIndex *> *_Nonnull moveSections;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + insertIndexPaths + +
    +
    +
    +
    +
    +
    +

    Clean item insert index paths.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic)
    +    NSSet<NSIndexPath *> *_Nonnull insertIndexPaths;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + deleteIndexPaths + +
    +
    +
    +
    +
    +
    +

    Clean item delete index paths.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic)
    +    NSSet<NSIndexPath *> *_Nonnull deleteIndexPaths;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + reloadIndexPaths + +
    +
    +
    +
    +
    +
    +

    Clean item reload index paths.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic)
    +    NSSet<NSIndexPath *> *_Nonnull reloadIndexPaths;
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Create a new batch update object with section and item operations.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull instancetype)
    +initWithInsertSections:(nonnull NSIndexSet *)insertSections
    +        deleteSections:(nonnull NSIndexSet *)deleteSections
    +          moveSections:(nonnull NSSet<IGListMoveIndex *> *)moveSections
    +      insertIndexPaths:(nonnull NSSet<NSIndexPath *> *)insertIndexPaths
    +      deleteIndexPaths:(nonnull NSSet<NSIndexPath *> *)deleteIndexPaths
    +      reloadIndexPaths:(nonnull NSSet<NSIndexPath *> *)reloadIndexPaths;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + insertSections + + +
    +

    Section indexes to insert.

    + +
    +
    + + deleteSections + + +
    +

    Section indexes to delete.

    + +
    +
    + + moveSections + + +
    +

    Section moves.

    + +
    +
    + + insertIndexPaths + + +
    +

    Item index paths to insert.

    + +
    +
    + + deleteIndexPaths + + +
    +

    Item index paths to delete.

    + +
    +
    + + reloadIndexPaths + + +
    +

    Item index paths to reload.

    + +
    +
    +
    +
    +

    Return Value

    +

    A new batch update object with cleaned update operations.

    + +
    +
    +
    +
  • +
  • +
    + + + + -init + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListBatchUpdateData : NSObject
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + +new + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListBatchUpdateData : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListCollectionView.html b/docs/Classes/IGListCollectionView.html new file mode 100644 index 000000000..a56382dd4 --- /dev/null +++ b/docs/Classes/IGListCollectionView.html @@ -0,0 +1,565 @@ + + + + IGListCollectionView Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListCollectionView

+
+
+
@interface IGListCollectionView : UICollectionView
+ +
+
+

This class is never actually used by the IGListKit infrastructure. It exists only to give compiler errors when editing +methods are called on the collection view returned by -[IGListAdapter collectionView].

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -reloadData + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -reloadSections: + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -insertSections: + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -deleteSections: + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -setDelegate: + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -setDataSource: + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -setBackgroundView: + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListCollectionView : UICollectionView
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListIndexPathResult.html b/docs/Classes/IGListIndexPathResult.html new file mode 100644 index 000000000..26026f4e9 --- /dev/null +++ b/docs/Classes/IGListIndexPathResult.html @@ -0,0 +1,583 @@ + + + + IGListIndexPathResult Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListIndexPathResult

+
+
+
@interface IGListIndexPathResult : NSObject
+ +
+
+

Result object returned when diffing with sections.

+ +
+
+
+
    +
  • +
    + + + + inserts + +
    +
    +
    +
    +
    +
    +

    Index paths inserted into the new collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, copy, nonatomic) NSArray<NSIndexPath *> *_Nonnull inserts;
    + +
    +
    +

    Swift

    +
    var inserts: [IndexPath] { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + deletes + +
    +
    +
    +
    +
    +
    +

    Index paths deleted from the old collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, copy, nonatomic) NSArray<NSIndexPath *> *_Nonnull deletes;
    + +
    +
    +

    Swift

    +
    var deletes: [IndexPath] { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + updates + +
    +
    +
    +
    +
    +
    +

    Index paths in the new collection that need updated.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, copy, nonatomic) NSArray<NSIndexPath *> *_Nonnull updates;
    + +
    +
    +

    Swift

    +
    var updates: [IndexPath] { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + moves + +
    +
    +
    +
    +
    +
    +

    Moves from an index path in the old collection to an index path in the new collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, copy, nonatomic)
    +    NSArray<IGListMoveIndexPath *> *_Nonnull moves;
    + +
    +
    +

    Swift

    +
    var moves: [Any] { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -hasChanges + +
    +
    +
    +
    +
    +
    +

    Convenience to query if the result has any changes.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (BOOL)hasChanges;
    + +
    +
    +

    Swift

    +
    func hasChanges() -> Bool
    + +
    +
    +
    +

    Return Value

    +

    YES if the result has changes, NO otherwise.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Fetch the index path of the object with identifier before the diff.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nullable NSIndexPath *)oldIndexPathForIdentifier:
    +    (nonnull id<NSObject>)identifier;
    + +
    +
    +

    Swift

    +
    func oldIndexPath(forIdentifier identifier: NSObjectProtocol) -> IndexPath?
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifier + + +
    +

    The diff identifier of the object. See -[IGListDiffable diffIdentifier].

    + +
    +
    +
    +
    +

    Return Value

    +

    The index path of the object before the diff, or nil.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Fetch the index path of the object with identifier after the diff.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nullable NSIndexPath *)newIndexPathForIdentifier:
    +    (nonnull id<NSObject>)identifier;
    + +
    +
    +

    Swift

    +
    func newIndexPath(forIdentifier identifier: NSObjectProtocol) -> IndexPath?
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifier + + +
    +

    The diff identifier of the object. See -[IGListDiffable diffIdentifier].

    + +
    +
    +
    +
    +

    Return Value

    +

    The index path of the object after the diff, or nil.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Create a new result object transforming index paths that are both moved and updated into delete and inserts.

    + +

    @discussion This is a convenience method for using a result object to perform UICollectionView and UITableView updates.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull IGListIndexPathResult *)resultWithUpdatedMovesAsDeleteInserts;
    + +
    +
    +

    Swift

    +
    func withUpdatedMovesAsDeleteInserts() -> IGListIndexPathResult
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -init + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListIndexPathResult : NSObject
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + +new + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListIndexPathResult : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListIndexSetResult.html b/docs/Classes/IGListIndexSetResult.html new file mode 100644 index 000000000..54671a4d8 --- /dev/null +++ b/docs/Classes/IGListIndexSetResult.html @@ -0,0 +1,580 @@ + + + + IGListIndexSetResult Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListIndexSetResult

+
+
+
@interface IGListIndexSetResult : NSObject
+ +
+
+

Result object returned when diffing with indexes.

+ +
+
+
+
    +
  • +
    + + + + inserts + +
    +
    +
    +
    +
    +
    +

    Indexes inserted into the new collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic) NSIndexSet *_Nonnull inserts;
    + +
    +
    +

    Swift

    +
    var inserts: IndexSet { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + deletes + +
    +
    +
    +
    +
    +
    +

    Indexes deleted from the old collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic) NSIndexSet *_Nonnull deletes;
    + +
    +
    +

    Swift

    +
    var deletes: IndexSet { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + updates + +
    +
    +
    +
    +
    +
    +

    Indexes in the new collection that need updated.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic) NSIndexSet *_Nonnull updates;
    + +
    +
    +

    Swift

    +
    var updates: IndexSet { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + moves + +
    +
    +
    +
    +
    +
    +

    Moves from an index in the old collection to an index in the new collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, copy, nonatomic) NSArray<IGListMoveIndex *> *_Nonnull moves;
    + +
    +
    +

    Swift

    +
    var moves: [Any] { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -hasChanges + +
    +
    +
    +
    +
    +
    +

    Convenience to query if the result has any changes.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (BOOL)hasChanges;
    + +
    +
    +

    Swift

    +
    func hasChanges() -> Bool
    + +
    +
    +
    +

    Return Value

    +

    YES if the result has changes, NO otherwise.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Fetch the index of the object with identifier before the diff.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (NSUInteger)oldIndexForIdentifier:(nonnull id<NSObject>)identifier;
    + +
    +
    +

    Swift

    +
    func oldIndex(forIdentifier identifier: NSObjectProtocol) -> UInt
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifier + + +
    +

    The diff identifier of the object. See -[IGListDiffable diffIdentifier].

    + +
    +
    +
    +
    +

    Return Value

    +

    The index of the object before the diff, or NSNotFound.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Fetch the index of the object with identifier after the diff.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (NSUInteger)newIndexForIdentifier:(nonnull id<NSObject>)identifier;
    + +
    +
    +

    Swift

    +
    func newIndex(forIdentifier identifier: NSObjectProtocol) -> UInt
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifier + + +
    +

    The diff identifier of the object. See -[IGListDiffable diffIdentifier].

    + +
    +
    +
    +
    +

    Return Value

    +

    The index of the object after the diff, or NSNotFound.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Create a new result object transforming indexes that are both moved and updated into delete and inserts.

    + +

    @discussion This is a convenience method for using a result object to perform UICollectionView and UITableView updates.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull IGListIndexSetResult *)resultWithUpdatedMovesAsDeleteInserts;
    + +
    +
    +

    Swift

    +
    func withUpdatedMovesAsDeleteInserts() -> IGListIndexSetResult
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -init + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListIndexSetResult : NSObject
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + +new + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListIndexSetResult : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListMoveIndex.html b/docs/Classes/IGListMoveIndex.html new file mode 100644 index 000000000..b930635c4 --- /dev/null +++ b/docs/Classes/IGListMoveIndex.html @@ -0,0 +1,331 @@ + + + + IGListMoveIndex Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListMoveIndex

+
+
+
@interface IGListMoveIndex : NSObject
+ +
+
+

An object representing a move between indexes.

+ +
+
+
+
    +
  • +
    + + + + from + +
    +
    +
    +
    +
    +
    +

    An index in the old collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, assign, nonatomic) NSUInteger from;
    + +
    +
    +

    Swift

    +
    var from: UInt { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + to + +
    +
    +
    +
    +
    +
    +

    An index in the new collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, assign, nonatomic) NSUInteger to;
    + +
    +
    +

    Swift

    +
    var to: UInt { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -init + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListMoveIndex : NSObject
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + +new + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListMoveIndex : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListMoveIndexPath.html b/docs/Classes/IGListMoveIndexPath.html new file mode 100644 index 000000000..c76edc04e --- /dev/null +++ b/docs/Classes/IGListMoveIndexPath.html @@ -0,0 +1,331 @@ + + + + IGListMoveIndexPath Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListMoveIndexPath

+
+
+
@interface IGListMoveIndexPath : NSObject
+ +
+
+

An object representing a move between indexes.

+ +
+
+
+
    +
  • +
    + + + + from + +
    +
    +
    +
    +
    +
    +

    An index path in the old collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic) NSIndexPath *_Nonnull from;
    + +
    +
    +

    Swift

    +
    var from: IndexPath { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + to + +
    +
    +
    +
    +
    +
    +

    An index path in the new collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, strong, nonatomic) NSIndexPath *_Nonnull to;
    + +
    +
    +

    Swift

    +
    var to: IndexPath { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -init + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListMoveIndexPath : NSObject
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + +new + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListMoveIndexPath : NSObject
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListSectionController.html b/docs/Classes/IGListSectionController.html new file mode 100644 index 000000000..cf1311928 --- /dev/null +++ b/docs/Classes/IGListSectionController.html @@ -0,0 +1,551 @@ + + + + IGListSectionController Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListSectionController

+
+
+
@interface IGListSectionController : NSObject
+ +
+
+

The base class for section controllers used in the list infra. This class is meant to be subclassed.

+ +
+
+
+
    +
  • +
    + + + + viewController + +
    +
    +
    +
    +
    +
    +

    The view controller housing the adapter that created this section controller.

    + +

    @discussion Use this view controller to push, pop, present, or do other custom transitions. It is considered very bad +practice to cast this to a known view controller and call methods on it other than for navigations and transitions.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, nonatomic, nullable) UIViewController *viewController;
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + collectionContext + +
    +
    +
    +
    +
    +
    +

    A context object for interacting with the collection i.e. accessing the collection size, dequeing cells, +reloading/inserting/deleting, etc.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, nonatomic, nullable) id<IGListCollectionContext>
    +    collectionContext;
    + +
    +
    +

    Swift

    +
    weak var collectionContext: AnyObject? { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + inset + +
    +
    +
    +
    +
    +
    +

    The margins used to lay out content in the section controller.

    + +
    +

    See

    + -[UICollectionViewFlowLayout sectionInset] + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (assign, readwrite, nonatomic) UIEdgeInsets inset;
    + +
    +
    +

    Swift

    +
    var inset: Int32 { get set }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + minimumLineSpacing + +
    +
    +
    +
    +
    +
    +

    The minimum spacing to use between rows of items.

    + +
    +

    See

    + -[UICollectionViewFlowLayout minimumLineSpacing] + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (assign, readwrite, nonatomic) CGFloat minimumLineSpacing;
    + +
    +
    +

    Swift

    +
    var minimumLineSpacing: Int32 { get set }
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    The minimum spacing to use between items in the same row.

    + +
    +

    See

    + -[UICollectionViewFlowLayout minimumInteritemSpacing] + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (assign, readwrite, nonatomic) CGFloat minimumInteritemSpacing;
    + +
    +
    +

    Swift

    +
    var minimumInteritemSpacing: Int32 { get set }
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    The supplementary view source for the section controller. Can be nil.

    + +

    @discussion You may wish to return self if your section controller implements this protocol.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) id<IGListSupplementaryViewSource>
    +    supplementaryViewSource;
    + +
    +
    +

    Swift

    +
    weak var supplementaryViewSource: AnyObject? { get set }
    + +
    +
    +
    +

    Return Value

    +

    An object that conforms to IGListSupplementaryViewSource or nil.

    + +
    +
    +
    +
  • +
  • +
    + + + + displayDelegate + +
    +
    +
    +
    +
    +
    +

    An object that handles display events for the section controller. Can be nil.

    + +

    @discussion You may wish to return self if your section controller implements this protocol.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) id<IGListDisplayDelegate>
    +    displayDelegate;
    + +
    +
    +

    Swift

    +
    weak var displayDelegate: AnyObject? { get set }
    + +
    +
    +
    +

    Return Value

    +

    An object that conforms to IGListDisplayDelegate or nil.

    + +
    +
    +
    +
  • +
  • +
    + + + + workingRangeDelegate + +
    +
    +
    +
    +
    +
    +

    An object that handles working range events for the section controller. Can be nil.

    + +

    @discussion You may wish to return self if your section controller implements this protocol.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) id<IGListWorkingRangeDelegate>
    +    workingRangeDelegate;
    + +
    +
    +

    Swift

    +
    weak var workingRangeDelegate: AnyObject? { get set }
    + +
    +
    +
    +

    Return Value

    +

    An object that conforms to IGListWorkingRangeDelegate or nil.

    + +
    +
    +
    +
  • +
  • +
    + + + + scrollDelegate + +
    +
    +
    +
    +
    +
    +

    An object that handles display events for the section controller. Can be nil.

    + +

    @discussion You may wish to return self if your section controller implements this protocol.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable) id<IGListScrollDelegate>
    +    scrollDelegate;
    + +
    +
    +

    Swift

    +
    weak var scrollDelegate: AnyObject? { get set }
    + +
    +
    +
    +

    Return Value

    +

    An object that conforms to IGListDisplayDelegate or nil.

    + +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListSingleSectionController.html b/docs/Classes/IGListSingleSectionController.html new file mode 100644 index 000000000..458069dee --- /dev/null +++ b/docs/Classes/IGListSingleSectionController.html @@ -0,0 +1,402 @@ + + + + IGListSingleSectionController Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListSingleSectionController

+
+
+
@interface IGListSingleSectionController
+    : IGListSectionController <IGListSectionType>
+ +
+
+

This section controller is meant to make building simple, single-cell feeds easier. By providing the type of cell, a block +to configure the cell, and a block to return the size of a cell, you can use an IGListAdapter-powered feed without +overcomplicating your architecture.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Create a new section controller for a given cell type that will always have only one cell when present in a feed.

    + +
    +

    Warning

    +

    Be VERY CAREFUL not to create retain cycles by holding strong references to: the object that owns the adapter +(usually self) or the IGListAdapter. Pass in locally scoped objects or use weak references!

    + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull instancetype)
    +initWithCellClass:(nonnull Class)cellClass
    +   configureBlock:
    +       (nonnull void (^)(id _Nonnull,
    +                         __kindof UICollectionViewCell *_Nonnull))configureBlock
    +        sizeBlock:
    +            (nonnull CGSize (^)(id<IGListCollectionContext> _Nonnull))sizeBlock;
    + +
    +
    +

    Swift

    +
    init!(cellClass: AnyClass!, configureBlock: (@escaping (Any?, UnsafeMutablePointer
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + cellClass + + +
    +

    The UICollectionViewCell subclass for the single cell.

    + +
    +
    + + configureBlock + + +
    +

    A block that configures the cell with the item given to the section controller.

    + +
    +
    + + sizeBlock + + +
    +

    A block that returns the size for the cell given the collection context.

    + +
    +
    +
    +
    +

    Return Value

    +

    A new section controller.

    + +
    +
    +
    +
  • +
  • +
    + + + + selectionDelegate + +
    +
    +
    +
    +
    +
    +

    An optional delegate that handles selection and deselection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readwrite, nonatomic, nullable)
    +    id<IGListSingleSectionControllerDelegate>
    +        selectionDelegate;
    + +
    +
    +

    Swift

    +
    weak var selectionDelegate: IGListSingleSectionControllerDelegate? { get set }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + -init + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListSingleSectionController
    +    : IGListSectionController <IGListSectionType>
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + +new + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListSingleSectionController
    +    : IGListSectionController <IGListSectionType>
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Classes/IGListStackedSectionController.html b/docs/Classes/IGListStackedSectionController.html new file mode 100644 index 000000000..c9d178678 --- /dev/null +++ b/docs/Classes/IGListStackedSectionController.html @@ -0,0 +1,332 @@ + + + + IGListStackedSectionController Class Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListStackedSectionController

+
+
+
@interface IGListStackedSectionController
+    : IGListSectionController <IGListSectionType>
+ +
+
+

This is a clustered section controller, composed of many child section controllers. It constructs and routes item-level +indexes to the appropriate child section controller with a local index. This lets you build section controllers made up +of individual units that can be shared and reused with other section controllers.

+ +

For example, you can create a Comments section controller that displays lists of text that is used alongside photo, +video, or slideshow section controllers. You then have four small and manageable section controllers instead of one +huge class.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Create a new stacked section controller.

    + +

    @discussion The order of the section controllers dictates the order in which they appear. The first section controller +that is the supplementary source decides which supplementary views get displayed.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (instancetype)initWithSectionControllers:
    +    (NSArray<IGListSectionController<IGListSectionType> *> *)sectionControllers;
    + +
    +
    +

    Swift

    +
    init!(sectionControllers: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + sectionControllers + + +
    +

    An array of section controllers that make up the stack.

    + +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + -init + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListStackedSectionController
    +    : IGListSectionController <IGListSectionType>
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + +new + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @interface IGListStackedSectionController
    +    : IGListSectionController <IGListSectionType>
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Constants.html b/docs/Constants.html new file mode 100644 index 000000000..aa34c31cb --- /dev/null +++ b/docs/Constants.html @@ -0,0 +1,244 @@ + + + + Constants Reference + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ + +
+ + + diff --git a/docs/Enums.html b/docs/Enums.html new file mode 100644 index 000000000..3c523c01a --- /dev/null +++ b/docs/Enums.html @@ -0,0 +1,276 @@ + + + + Enums Reference + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

Enums

+

The following enums are available globally.

+ +
+
+
+
    +
  • +
    + + + + IGListDiffOption + +
    +
    +
    +
    +
    +
    +

    An option on how to do comparisons between similar objects.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    enum IGListDiffOption : NSUInteger {}
    + +
    +
    +

    Swift

    +
    enum IGListDiffOption : UInt
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListExperiment + +
    +
    +
    +
    +
    +
    +

    Bitmask-able options used for prerelease feature testing.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    enum IGListExperiment : NSUInteger {}
    + +
    +
    +

    Swift

    +
    struct IGListExperiment : OptionSet
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Enums/IGListDiffOption.html b/docs/Enums/IGListDiffOption.html new file mode 100644 index 000000000..74de80429 --- /dev/null +++ b/docs/Enums/IGListDiffOption.html @@ -0,0 +1,277 @@ + + + + IGListDiffOption Enum Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListDiffOption

+
+
+
enum IGListDiffOption : NSUInteger {}
+ +
+
+

An option on how to do comparisons between similar objects.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Compare objects using pointer personality.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    IGListDiffPointerPersonality
    + +
    +
    +

    Swift

    +
    case pointerPersonality = 0
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + IGListDiffEquality + +
    +
    +
    +
    +
    +
    +

    Compare objects using -[NSObject isEqual:].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    IGListDiffEquality
    + +
    +
    +

    Swift

    +
    case equality = 1
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Enums/IGListExperiment.html b/docs/Enums/IGListExperiment.html new file mode 100644 index 000000000..c8789204c --- /dev/null +++ b/docs/Enums/IGListExperiment.html @@ -0,0 +1,267 @@ + + + + IGListExperiment Enum Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListExperiment

+
+
+
enum IGListExperiment : NSUInteger {}
+ +
+
+

Bitmask-able options used for prerelease feature testing.

+ +
+
+
+ +
+
+
+ +
+
+ + + diff --git a/docs/Functions.html b/docs/Functions.html new file mode 100644 index 000000000..5a95a67a5 --- /dev/null +++ b/docs/Functions.html @@ -0,0 +1,529 @@ + + + + Functions Reference + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

Functions

+

The following functions are available globally.

+ +
+
+
+
    +
  • +
    + + + + IGListDiff + +
    +
    +
    +
    +
    +
    +

    Create a diff using indexes between two collections.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    extern IGListIndexSetResult *_Nonnull IGListDiff(
    +    NSArray<id<IGListDiffable>> *_Nullable oldArray,
    +    NSArray<id<IGListDiffable>> *_Nullable newArray, IGListDiffOption option)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + oldArray + + +
    +

    The old objects to diff against.

    + +
    +
    + + newArray + + +
    +

    The new objects to diff with.

    + +
    +
    + + option + + +
    +

    An option on how to compare objects.

    + +
    +
    +
    +
    +

    Return Value

    +

    Result object with effected indexes.

    + +
    +
    +
    +
  • +
  • +
    + + + + IGListDiffPaths + +
    +
    +
    +
    +
    +
    +

    Create a diff using index paths between two collections.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    extern IGListIndexPathResult *_Nonnull IGListDiffPaths(
    +    NSInteger fromSection, NSInteger toSection,
    +    NSArray<id<IGListDiffable>> *_Nullable oldArray,
    +    NSArray<id<IGListDiffable>> *_Nullable newArray, IGListDiffOption option)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + fromSection + + +
    +

    The old section used to seed returned index paths.

    + +
    +
    + + toSection + + +
    +

    The new section used to seed returned index paths.

    + +
    +
    + + oldArray + + +
    +

    The old objects to diff against.

    + +
    +
    + + newArray + + +
    +

    The new objects to diff with.

    + +
    +
    + + option + + +
    +

    An option on how to compare objects.

    + +
    +
    +
    +
    +

    Return Value

    +

    Result object with effected index paths.

    + +
    +
    +
    +
  • +
+
+
+ +
+
+
+ +
+
+ + + diff --git a/docs/Protocols.html b/docs/Protocols.html new file mode 100644 index 000000000..89bffe712 --- /dev/null +++ b/docs/Protocols.html @@ -0,0 +1,651 @@ + + + + Protocols Reference + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

Protocols

+

The following protocols are available globally.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Implement this protocol to provide data to power an IGListAdapter feed.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListAdapterDataSource <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListAdapterDataSource
    + +
    +
    +
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
    +
    +
    +
    +

    A protocol that receives events about IGListAdapterUpdater operations.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListAdapterUpdaterDelegate <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListAdapterUpdaterDelegate
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    The collection context provides limited access to the collection related information that +section controllers need for things like sizing, dequeing cells, insterting/deleting/reloading, etc.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListCollectionContext <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListCollectionContext
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListDiffable + +
    +
    +
    +
    +
    +
    +

    The IGListDiffable protocol provides the base methods needed to compare the identity and equality of two objects using +one of the IGKAlgorithm functions.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListDiffable
    + +
    +
    +

    Swift

    +
    protocol IGListDiffable
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListDisplayDelegate + +
    +
    +
    +
    +
    +
    +

    Implement this protocol to receive display events for an section controller when it is on screen.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListDisplayDelegate <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListDisplayDelegate
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListScrollDelegate + +
    +
    +
    +
    +
    +
    +

    Implement this protocol to receive display events for an section controller when it is on screen.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListScrollDelegate <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListScrollDelegate
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • +
    + + + + IGListSectionType + +
    +
    +
    +
    +
    +
    +

    Implement this protocol in order to be used within the IGListKit data infrastructure and be registered for use in an +IGListAdapter. An IGListSectionType conforming object represents a single instance of an object in a collection of +objects.

    + +

    The infrastructure uses each IGListSectionType conforming object as a view model to populate and control cells as +part of a section in a UICollectionView feed. IGListSectionType objects should be architected without knowledge of +global state of the feed they are contained in.

    + +

    Index paths are used as a convenience for communicating the section index to each section object without allowing each +section to mutate its own position within a feed. The row of an index path can be directly mapped to a cell within +an IGListSectionType conforming object.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListSectionType <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListSectionType
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    A delegate that can receive selection events on an IGListSingleSectionController.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListSingleSectionControllerDelegate <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListSingleSectionControllerDelegate
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Implement the methods of this protocol to provide information about a list’s supplementary views. This data is used in +IGListAdapter which then configures and maintains a UICollectionView. The supplementary API reflects that in +UICollectionView, UICollectionViewLayout, and UICollectionViewDataSource.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListSupplementaryViewSource <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListSupplementaryViewSource
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Implement this protocol in order to handle both section and row based update events. Implementation should forward or +coalesce these events to a backing store or collection.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListUpdatingDelegate
    + +
    +
    +

    Swift

    +
    protocol IGListUpdatingDelegate
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Implement this protocol to receive working range events for a list.

    + +

    The working range is a range near the viewport in which you can begin preparing content for display. For example, +you could begin decoding images, or warming text caches.

    + + See more +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @protocol IGListWorkingRangeDelegate <NSObject>
    + +
    +
    +

    Swift

    +
    protocol IGListWorkingRangeDelegate
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListAdapterDataSource.html b/docs/Protocols/IGListAdapterDataSource.html new file mode 100644 index 000000000..b558b1840 --- /dev/null +++ b/docs/Protocols/IGListAdapterDataSource.html @@ -0,0 +1,407 @@ + + + + IGListAdapterDataSource Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListAdapterDataSource

+
+
+
@protocol IGListAdapterDataSource <NSObject>
+ +
+
+

Implement this protocol to provide data to power an IGListAdapter feed.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Asks the data source for an array of objects for each list in your feed.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull NSArray<id<IGListDiffable>> *)objectsForListAdapter:
    +    (nonnull IGListAdapter *)listAdapter;
    + +
    +
    +

    Swift

    +
    func objects(for listAdapter: IGListAdapter!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter requesting this information.

    + +
    +
    +
    +
    +

    Return Value

    +

    An array of objects for the feed.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Asks the data source for a section controller for the specified data object.

    + +

    @discussion New section controllers should be initialized here for objects when asked. You may pass any other data to +the section controller at this time.

    + +

    Section controllers are initialized for all objects whenever the IGListAdapter is created, updated, or reloaded. +Section controllers are reused when objects are moved or updated. Maintaining the -[IGListDiffable diffIdentifier] +gauruntees this.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull IGListSectionController<IGListSectionType> *)
    +               listAdapter:(nonnull IGListAdapter *)listAdapter
    +sectionControllerForObject:(nonnull id)object;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter requesting this information.

    + +
    +
    + + object + + +
    +

    An object in the feed, provided in -objectsForListAdapter:.

    + +
    +
    +
    +
    +

    Return Value

    +

    An IGListSectionType conforming object that can be displayed in the feed.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Asks the data source for a view to use as the collection view background when there are no objects.

    + +

    @discussion This method is called every time the list adapter is updated. You are free to return new views every time, +but for performance reasons you may want to retain your own view and return it here. The infra is only responsible for +adding the background view and maintaining its visibility.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nullable UIView *)emptyViewForListAdapter:
    +    (nonnull IGListAdapter *)listAdapter;
    + +
    +
    +

    Swift

    +
    func emptyView(for listAdapter: IGListAdapter!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter requesting this information.

    + +
    +
    +
    +
    +

    Return Value

    +

    A view to use as the collection view background, or nil if you don’t want a background view.

    + +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListAdapterDelegate.html b/docs/Protocols/IGListAdapterDelegate.html new file mode 100644 index 000000000..713f66262 --- /dev/null +++ b/docs/Protocols/IGListAdapterDelegate.html @@ -0,0 +1,367 @@ + + + + IGListAdapterDelegate Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListAdapterDelegate

+

Undocumented

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that a list object is about to be displayed

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(IGListAdapter *)listAdapter
    +    willDisplayObject:(id)object
    +              atIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func listAdapter(_ listAdapter: IGListAdapter!, willDisplay object: Any!, at index: Int)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter sending this information.

    + +
    +
    + + object + + +
    +

    The object that will display.

    + +
    +
    + + index + + +
    +

    The index of the object in the list.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that a list item is no longer being displayed

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(IGListAdapter *)listAdapter
    +    didEndDisplayingObject:(id)object
    +                   atIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func listAdapter(_ listAdapter: IGListAdapter!, didEndDisplaying object: Any!, at index: Int)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter sending this information.

    + +
    +
    + + object + + +
    +

    The object that ended display.

    + +
    +
    + + index + + +
    +

    The index of the item/object in the list.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListAdapterUpdaterDelegate.html b/docs/Protocols/IGListAdapterUpdaterDelegate.html new file mode 100644 index 000000000..ccc6bfe48 --- /dev/null +++ b/docs/Protocols/IGListAdapterUpdaterDelegate.html @@ -0,0 +1,917 @@ + + + + IGListAdapterUpdaterDelegate Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListAdapterUpdaterDelegate

+
+
+
@protocol IGListAdapterUpdaterDelegate <NSObject>
+ +
+
+

A protocol that receives events about IGListAdapterUpdater operations.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the updater will call -[UICollectionView performBatchUpdates:completion:].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +    willPerformBatchUpdatesWithCollectionView:
    +        (nonnull UICollectionView *)collectionView;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, willPerformBatchUpdatesWithCollectionView collectionView: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + collectionView + + +
    +

    The collection view that will perform the batch updates.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the updater succesfully finished -[UICollectionView performBatchUpdates:completion:].

    + +

    @discussion This event is called in the completion block of the batch update.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +    didPerformBatchUpdates:(nonnull IGListBatchUpdateData *)updates
    +        withCollectionView:(nonnull UICollectionView *)collectionView;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, didPerformBatchUpdates updates: Any!, withCollectionView collectionView: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + collectionView + + +
    +

    The collection view that performed the batch updates.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the updater will call -[UICollectionView insertItemsAtIndexPaths:].

    + +

    @discussion This event is only sent when outside of -[UICollectionView performBatchUpdates:completion:].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +      willInsertIndexPaths:(nonnull NSArray<NSIndexPath *> *)indexPaths
    +            collectionView:(nonnull UICollectionView *)collectionView;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, willInsertIndexPaths indexPaths: Any!, collectionView: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + indexPaths + + +
    +

    An array of index paths that will be inserted.

    + +
    +
    + + collectionView + + +
    +

    The collection view that will perform the insert.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the updater will call -[UICollectionView deleteItemsAtIndexPaths:].

    + +

    @discussion This event is only sent when outside of -[UICollectionView performBatchUpdates:completion:].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +      willDeleteIndexPaths:(nonnull NSArray<NSIndexPath *> *)indexPaths
    +            collectionView:(nonnull UICollectionView *)collectionView;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, willDeleteIndexPaths indexPaths: Any!, collectionView: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + indexPaths + + +
    +

    An array of index paths that will be deleted.

    + +
    +
    + + collectionView + + +
    +

    The collection view that will perform the delete.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the updater will call -[UICollectionView reloadItemsAtIndexPaths:].

    + +

    @discussion This event is only sent when outside of -[UICollectionView performBatchUpdates:completion:].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +      willReloadIndexPaths:(nonnull NSArray<NSIndexPath *> *)indexPaths
    +            collectionView:(nonnull UICollectionView *)collectionView;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, willReloadIndexPaths indexPaths: Any!, collectionView: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + indexPaths + + +
    +

    An array of index paths that will be reloaded.

    + +
    +
    + + collectionView + + +
    +

    The collection view that will perform the reload.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the updater will call -[UICollectionView reloadSections:].

    + +

    @discussion This event is only sent when outside of -[UICollectionView performBatchUpdates:completion:].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +        willReloadSections:(nonnull NSIndexSet *)sections
    +            collectionView:(nonnull UICollectionView *)collectionView;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, willReloadSections sections: Any!, collectionView: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + sections + + +
    +

    The sections that will be reloaded

    + +
    +
    + + collectionView + + +
    +

    The collection view that will perform the reload.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the updater will call -[UICollectionView reloadData].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +    willReloadDataWithCollectionView:(nonnull UICollectionView *)collectionView;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, willReloadDataWithCollectionView collectionView: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + collectionView + + +
    +

    The collection view that will be reloaded.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the updater successfully called -[UICollectionView reloadData].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +    didReloadDataWithCollectionView:(nonnull UICollectionView *)collectionView;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, didReloadDataWithCollectionView collectionView: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + collectionView + + +
    +

    The collection view that reloaded.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that the collection view threw an exception in -[UICollectionView performBatchUpdates:completion:].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapterUpdater:(nonnull IGListAdapterUpdater *)listAdapterUpdater
    +    willCrashWithException:(nonnull NSException *)exception
    +               fromObjects:(nullable NSArray *)fromObjects
    +                 toObjects:(nullable NSArray *)toObjects
    +                   updates:(nonnull IGListBatchUpdateData *)updates;
    + +
    +
    +

    Swift

    +
    func listAdapterUpdater(_ listAdapterUpdater: IGListAdapterUpdater!, willCrashWithException exception: Any!, fromObjects: Any!, toObjects: Any!, updates: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + listAdapterUpdater + + +
    +

    The adapter updater owning the transition.

    + +
    +
    + + exception + + +
    +

    The exception thrown by the collection view.

    + +
    +
    + + fromObjects + + +
    +

    The items transitioned from in the diff, if any.

    + +
    +
    + + toObjects + + +
    +

    The items transitioned to in the diff, if any.

    + +
    +
    + + updates + + +
    +

    The batch updates that were applied to the collection view.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListCollectionContext.html b/docs/Protocols/IGListCollectionContext.html new file mode 100644 index 000000000..14282c9a1 --- /dev/null +++ b/docs/Protocols/IGListCollectionContext.html @@ -0,0 +1,1150 @@ + + + + IGListCollectionContext Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListCollectionContext

+
+
+
@protocol IGListCollectionContext <NSObject>
+ +
+
+

The collection context provides limited access to the collection related information that +section controllers need for things like sizing, dequeing cells, insterting/deleting/reloading, etc.

+ +
+
+
+
    +
  • +
    + + + + containerSize + +
    +
    +
    +
    +
    +
    +

    Size of the collection view. Provided primarily for sizing cells.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    @property (readonly, nonatomic) CGSize containerSize;
    + +
    +
    +

    Swift

    +
    var containerSize: Int32 { get }
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Query for the index a cell in the collection relative to the section controller.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (NSUInteger)indexForCell:(nonnull UICollectionViewCell *)cell
    +         sectionController:
    +             (nonnull IGListSectionController<IGListSectionType> *)
    +                 sectionController;
    + +
    +
    +

    Swift

    +
    func index(forCell cell: Any!, sectionController: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + cell + + +
    +

    An existing cell in the collection.

    + +
    +
    + + sectionController + + +
    +

    The section controller requesting this information.

    + +
    +
    +
    +
    +

    Return Value

    +

    The index of the cell or NSNotFound if it does not exist in the collection.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Query for a cell in the collection. May return nil if the cell is offscreen.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nullable __kindof UICollectionViewCell *)
    +cellForItemAtIndex:(NSInteger)index
    + sectionController:
    +     (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +

    Swift

    +
    func cellForItem(atIndex index: Any!, sectionController: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + index + + +
    +

    The index of the desired cell.

    + +
    +
    + + sectionController + + +
    +

    The section controller requesting this information.

    + +
    +
    +
    +
    +

    Return Value

    +

    The collection view cell, or nil if not found.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Query for the visible cells for the given section controller.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull NSArray<UICollectionViewCell *> *)visibleCellsForSectionController:
    +    (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +

    Swift

    +
    func visibleCells(forSectionController sectionController: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + sectionController + + +
    +

    The section controller requesting this information.

    + +
    +
    +
    +
    +

    Return Value

    +

    An array of visible cells, or an empty array if none are found.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Deselects a cell in the collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)deselectItemAtIndex:(NSInteger)index
    +          sectionController:
    +              (nonnull IGListSectionController<IGListSectionType> *)
    +                  sectionController
    +                   animated:(BOOL)animated;
    + +
    +
    +

    Swift

    +
    func deselectItem(atIndex index: Any!, sectionController: Any!, animated: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + index + + +
    +

    The index of the item to deselect.

    + +
    +
    + + sectionController + + +
    +

    The section controller requesting this information.

    + +
    +
    + + animated + + +
    +

    Pass YES to animate the change, NO otherwise.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Query the section index of an section controller.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (NSUInteger)sectionForSectionController:
    +    (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +

    Swift

    +
    func section(forSectionController sectionController: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + sectionController + + +
    +

    An section controller object.

    + +
    +
    +
    +
    +

    Return Value

    +

    The section index of the list if found, otherwise NSNotFound.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Dequeues a cell from the UICollectionView reuse pool.

    + +
    +

    Note

    +

    This method uses a string representation of the cell class as the identifier.

    + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull __kindof UICollectionViewCell *)
    +dequeueReusableCellOfClass:(nonnull Class)cellClass
    +      forSectionController:
    +          (nonnull IGListSectionController<IGListSectionType> *)
    +              sectionController
    +                   atIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func dequeueReusableCell(of cellClass: AnyClass!, forSectionController sectionController: Any!, atIndex index: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + cellClass + + +
    +

    The class of the cell you want to dequeue.

    + +
    +
    + + sectionController + + +
    +

    The section controller requesting this information.

    + +
    +
    + + index + + +
    +

    The index of the cell.

    + +
    +
    +
    +
    +

    Return Value

    +

    A cell dequeued from the reuse pool or newly created.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Dequeues a supplementary view from the UICollectionView reuse pool.

    + +
    +

    Note

    +

    This method uses a string representation of the view class as the identifier.

    + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull __kindof UICollectionReusableView *)
    +dequeueReusableSupplementaryViewOfKind:(nonnull NSString *)elementKind
    +                  forSectionController:
    +                      (nonnull IGListSectionController<IGListSectionType> *)
    +                          sectionController
    +                                 class:(nonnull Class)viewClass
    +                               atIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func dequeueReusableSupplementaryView(ofKind elementKind: Any!, forSectionController sectionController: Any!, class viewClass: AnyClass!, atIndex index: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + +
    + + elementKind + + +
    +

    The kind of supplementary veiw.

    + +
    +
    + + sectionController + + +
    +

    The section controller requesting this information.

    + +
    +
    + + viewClass + + +
    +

    The class of the supplementary view.

    + +
    +
    + + index + + +
    +

    The index of the supplementary vew.

    + +
    +
    +
    +
    +

    Return Value

    +

    A supplementary view dequeued from the reuse pool or newly created.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Reloads cells in the section controller.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)reloadInSectionController:
    +            (nonnull IGListSectionController<IGListSectionType> *)
    +                sectionController
    +                        atIndexes:(nonnull NSIndexSet *)indexes;
    + +
    +
    +

    Swift

    +
    func reload(inSectionController sectionController: Any!, atIndexes indexes: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + sectionController + + +
    +

    The section controller who’s cells need reloading.

    + +
    +
    + + indexes + + +
    +

    The indexes of items that need reloading.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Inserts cells in the feed.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)insertInSectionController:
    +            (nonnull IGListSectionController<IGListSectionType> *)
    +                sectionController
    +                        atIndexes:(nonnull NSIndexSet *)indexes;
    + +
    +
    +

    Swift

    +
    func insert(inSectionController sectionController: Any!, atIndexes indexes: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + sectionController + + +
    +

    The section controller who’s cells need inserting.

    + +
    +
    + + indexes + + +
    +

    The indexes of items that need inserting.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Deletes cells in the feed.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)deleteInSectionController:
    +            (nonnull IGListSectionController<IGListSectionType> *)
    +                sectionController
    +                        atIndexes:(nonnull NSIndexSet *)indexes;
    + +
    +
    +

    Swift

    +
    func delete(inSectionController sectionController: Any!, atIndexes indexes: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + sectionController + + +
    +

    The section controller who’s cells need deleted.

    + +
    +
    + + indexes + + +
    +

    The indexes of items that need deleting.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Reload the entire section controller.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)reloadSectionController:
    +    (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +

    Swift

    +
    func reloadSectionController(_ sectionController: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + sectionController + + +
    +

    The list object who’s cells need reloading.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Batch many cell-level updates in a single transaction.

    + +

    @discussion Use this method to batch cell updates (inserts, deletes, reloads) into a single transaction. This lets you +make many changes to your data store and perform all the transitions at once.

    + +

    For example, inside your section controllers, you may want to delete /and/ insert into the data source that backs your +section controller:

    + +

    [self.collectionContext performBatchItemUpdates:^{ +// perform data source changes inside the update block +[self.items addObject:newItem]; +[self.items removeObjectAtIndex:0];

    + +

    NSIndexSet *inserts = [NSIndexSet indexSetWithIndex:[self.items count] - 1]; +[self.collectionContext insertInSectionController:self atIndexes:inserts];

    + +

    NSIndexSet *deletes = [NSIndexSet indexSetWithIndex:0]; +[self.collectionContext deleteInSectionController:self deletes]; +} completion:nil];

    + +

    Note that you must perform data modifications inside the update block. Updates will not be performed +synchronously, so you should make sure that your data source changes only when necessary.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)performBatchAnimated:(BOOL)animated
    +                     updates:(nonnull void (^)())updates
    +                  completion:(nullable void (^)(BOOL))completion;
    + +
    +
    +

    Swift

    +
    func performBatchAnimated(_ animated: Any!, updates: (@escaping () -> Void)!, completion: (@escaping (Int32) -> Void)? = nil)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + animated + + +
    +

    A flag indicating if the transition should be animated.

    + +
    +
    + + updates + + +
    +

    A block containing all of the cell updates.

    + +
    +
    + + completion + + +
    +

    An optional completion block to execute when the updates are finished.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListDiffable.html b/docs/Protocols/IGListDiffable.html new file mode 100644 index 000000000..46503bc27 --- /dev/null +++ b/docs/Protocols/IGListDiffable.html @@ -0,0 +1,329 @@ + + + + IGListDiffable Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListDiffable

+
+
+
@protocol IGListDiffable
+ +
+
+

The IGListDiffable protocol provides the base methods needed to compare the identity and equality of two objects using +one of the IGKAlgorithm functions.

+ +
+
+
+
    +
  • +
    + + + + -diffIdentifier + +
    +
    +
    +
    +
    +
    +

    Returns a key that uniquely identifies the object.

    + +

    @discussion Two objects may share the same identifier, but are not equal. A common pattern is to import IGListCommon.h +and use the NSObject category for automatic conformance. However this means that objects will be identified on their +pointer value so finding updates becomes impossible.

    + +
    +

    Warning

    + This value should never be mutated. + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull id<NSObject>)diffIdentifier;
    + +
    +
    +

    Swift

    +
    func diffIdentifier() -> NSObjectProtocol
    + +
    +
    +
    +

    Return Value

    +

    A key that can be used to uniquely identify the object.

    + +
    +
    +
    +
  • +
  • +
    + + + + -isEqual: + +
    +
    +
    +
    +
    +
    +

    Returns a Boolean value that indicates whether the receiver and a given object are equal.

    + +
    +

    Warning

    +

    If you implement a custom isEqual: you must also implement -hash. You can just use the -diffIdentifier value +for your hash function:

    + +
    + +

  • (NSUInteger)hash { +return [[self diffIdentifier] hash]; +}

  • + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (BOOL)isEqual:(nullable id<IGListDiffable>)object;
    + +
    +
    +

    Swift

    +
    func isEqual(_ object: IGListDiffable?) -> Bool
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + object + + +
    +

    The object to be compared to the receiver.

    + +
    +
    +
    +
    +

    Return Value

    +

    YES if the receiver and object are equal, otherwise NO.

    + +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListDisplayDelegate.html b/docs/Protocols/IGListDisplayDelegate.html new file mode 100644 index 000000000..ba5eebb19 --- /dev/null +++ b/docs/Protocols/IGListDisplayDelegate.html @@ -0,0 +1,517 @@ + + + + IGListDisplayDelegate Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListDisplayDelegate

+
+
+
@protocol IGListDisplayDelegate <NSObject>
+ +
+
+

Implement this protocol to receive display events for an section controller when it is on screen.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate that the specified list is about to be displayed.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(nonnull IGListAdapter *)listAdapter
    +    willDisplaySectionController:
    +        (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter that the list will display in.

    + +
    +
    + + sectionController + + +
    +

    The list about to be displayed.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate that the specified list is no longer being displayed.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(nonnull IGListAdapter *)listAdapter
    +    didEndDisplayingSectionController:
    +        (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter that the list was displayed in.

    + +
    +
    + + sectionController + + +
    +

    The list that is no longer displayed.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate that a row in the specified list is about to be displayed.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(nonnull IGListAdapter *)listAdapter
    +    willDisplaySectionController:
    +        (nonnull IGListSectionController<IGListSectionType> *)sectionController
    +                            cell:(nonnull UICollectionViewCell *)cell
    +                         atIndex:(NSInteger)index;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter that row will display in.

    + +
    +
    + + sectionController + + +
    +

    The section controller that is displaying.

    + +
    +
    + + cell + + +
    +

    The cell about to be displayed.

    + +
    +
    + + index + + +
    +

    The index of the row.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate that a row in the specified list is no longer being displayed.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(nonnull IGListAdapter *)listAdapter
    +    didEndDisplayingSectionController:
    +        (nonnull IGListSectionController<IGListSectionType> *)sectionController
    +                                 cell:(nonnull UICollectionViewCell *)cell
    +                              atIndex:(NSInteger)index;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter that the list was displayed in.

    + +
    +
    + + sectionController + + +
    +

    The section controller that is no longer displaying the cell.

    + +
    +
    + + cell + + +
    +

    The cell that is no longer displayed.

    + +
    +
    + + index + + +
    +

    The index of the row.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListScrollDelegate.html b/docs/Protocols/IGListScrollDelegate.html new file mode 100644 index 000000000..6b485570c --- /dev/null +++ b/docs/Protocols/IGListScrollDelegate.html @@ -0,0 +1,400 @@ + + + + IGListScrollDelegate Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListScrollDelegate

+
+
+
@protocol IGListScrollDelegate <NSObject>
+ +
+
+

Implement this protocol to receive display events for an section controller when it is on screen.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate that the section controller was scrolled on screen.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(IGListAdapter *)listAdapter
    +    didScrollSectionController:
    +        (IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter whose collection view was scrolled.

    + +
    +
    + + sectionController + + +
    +

    The visible section controller that was scrolled.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate that the section controller will be dragged on screen.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(IGListAdapter *)listAdapter
    +    willBeginDraggingSectionController:
    +        (IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter whose collection view will drag.

    + +
    +
    + + sectionController + + +
    +

    The visible section controller that will drag.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate that the section controller did end dragging on screen.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(IGListAdapter *)listAdapter
    +    didEndDraggingSectionController:
    +        (IGListSectionController<IGListSectionType> *)sectionController
    +                     willDecelerate:(BOOL)decelerate;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The list adapter whose collection view ended dragging.

    + +
    +
    + + sectionController + + +
    +

    The visible section controller that ended dragging.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListSectionType.html b/docs/Protocols/IGListSectionType.html new file mode 100644 index 000000000..1d4799dc2 --- /dev/null +++ b/docs/Protocols/IGListSectionType.html @@ -0,0 +1,497 @@ + + + + IGListSectionType Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListSectionType

+
+
+
@protocol IGListSectionType <NSObject>
+ +
+
+

Implement this protocol in order to be used within the IGListKit data infrastructure and be registered for use in an +IGListAdapter. An IGListSectionType conforming object represents a single instance of an object in a collection of +objects.

+ +

The infrastructure uses each IGListSectionType conforming object as a view model to populate and control cells as +part of a section in a UICollectionView feed. IGListSectionType objects should be architected without knowledge of +global state of the feed they are contained in.

+ +

Index paths are used as a convenience for communicating the section index to each section object without allowing each +section to mutate its own position within a feed. The row of an index path can be directly mapped to a cell within +an IGListSectionType conforming object.

+ +
+
+
+
    +
  • +
    + + + + -numberOfItems + +
    +
    +
    +
    +
    +
    +

    The number of items in the IGListSectionType.

    + +

    @discussion The count returned is used to drive the number of cells displayed for this list. You are free to change +this value between data loading passes.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (NSUInteger)numberOfItems;
    + +
    +
    +

    Swift

    +
    func numberOfItems() -> Any!
    + +
    +
    +
    +

    Return Value

    +

    A count of items in the list.

    + +
    +
    +
    +
  • +
  • +
    + + + + -sizeForItemAtIndex: + +
    +
    +
    +
    +
    +
    +

    The specific size for the item at the specified index.

    + +

    @discussion The returned size is not garaunteed to be used. The feed implementation may query list items for their +layout information at will, or use its own layout metrics. For example, consider a dynamic-text sized feed vs. a fixed +height-and-width grid feed. The former will ask each IGListSectionType for a size, and the latter will likely not.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (CGSize)sizeForItemAtIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func sizeForItem(atIndex index: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + index + + +
    +

    The row index of the item.

    + +
    +
    +
    +
    +

    Return Value

    +

    The size for the item at index.

    + +
    +
    +
    +
  • +
  • +
    + + + + -cellForItemAtIndex: + +
    +
    +
    +
    +
    +
    +

    Asks the section controller for a fully configured cell at an index path.

    + +

    @discussion This is your opportunity to do any cell setup and configuration. The infrastructure requests a cell when it +will be used on screen. You should never allocate new cells in this method, instead on the provided adapter call +-dequeCellClass:forIndexPath: which either deques a cell from the UICollectionView or creates a new one for you.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull __kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func cellForItem(atIndex index: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + index + + +
    +

    The index of the requested row.

    + +
    +
    +
    +
    +

    Return Value

    +

    A configured UICollectionViewCell subclass.

    + +
    +
    +
    +
  • +
  • +
    + + + + -didUpdateToObject: + +
    +
    +
    +
    +
    +
    +

    Tells the IGListSectionType that the section controller was updated to a new item.

    + +

    @discussion When this method is called, all available contexts and configurations have been set for the section +controller. Also, depending on the updating strategy used, your item models may have changed objects in memory, so you +can use this event to update the object stored on your section controller.

    + +

    This method will only be called when the object instance has changed, either from nil or a previous object.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)didUpdateToObject:(nonnull id)object;
    + +
    +
    +

    Swift

    +
    func didUpdate(to object: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + object + + +
    +

    The object mapped to this section controller.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the IGListSectionType that the cell at the specified index path was selected.

    + +

    @discussion Implementation of this method is required for compile-time safety, but you are free to do nothing.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)didSelectItemAtIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func didSelectItem(atIndex index: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + index + + +
    +

    The index of the selected cell.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListSingleSectionControllerDelegate.html b/docs/Protocols/IGListSingleSectionControllerDelegate.html new file mode 100644 index 000000000..4ad13606e --- /dev/null +++ b/docs/Protocols/IGListSingleSectionControllerDelegate.html @@ -0,0 +1,266 @@ + + + + IGListSingleSectionControllerDelegate Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListSingleSectionControllerDelegate

+
+
+
@protocol IGListSingleSectionControllerDelegate <NSObject>
+ +
+
+

A delegate that can receive selection events on an IGListSingleSectionController.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate that the section controller was selected.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)didSelectSingleSectionController:
    +    (nonnull IGListSingleSectionController *)sectionController;
    + +
    +
    +

    Swift

    +
    func didSelectSingleSectionController(_ sectionController: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + sectionController + + +
    +

    The section controller that was selected.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListSupplementaryViewSource.html b/docs/Protocols/IGListSupplementaryViewSource.html new file mode 100644 index 000000000..1b43e3154 --- /dev/null +++ b/docs/Protocols/IGListSupplementaryViewSource.html @@ -0,0 +1,398 @@ + + + + IGListSupplementaryViewSource Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListSupplementaryViewSource

+
+
+
@protocol IGListSupplementaryViewSource <NSObject>
+ +
+
+

Implement the methods of this protocol to provide information about a list’s supplementary views. This data is used in +IGListAdapter which then configures and maintains a UICollectionView. The supplementary API reflects that in +UICollectionView, UICollectionViewLayout, and UICollectionViewDataSource.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Asks the SupplementaryViewSource for an array of supported element kinds.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull NSArray<NSString *> *)supportedElementKinds;
    + +
    +
    +

    Swift

    +
    func supportedElementKinds() -> Any!
    + +
    +
    +
    +

    Return Value

    +

    An array of element kind strings that the supplementary source handles.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Asks the SupplementaryViewSource for a configured supplementary view for the specified kind and index.

    + +

    @discussion This is your opportunity to do any supplementary view setup and configuration.

    + +
    +

    Warning

    + You should never allocate new views in this method. Instead deque a view from the IGListCollectionContext. + +
    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull __kindof UICollectionReusableView *)
    +viewForSupplementaryElementOfKind:(nonnull NSString *)elementKind
    +                          atIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func viewForSupplementaryElement(ofKind elementKind: Any!, atIndex index: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + elementKind + + +
    +

    The kind of supplementary view being requested

    + +
    +
    + + index + + +
    +

    The index for the row being requested.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Asks the SupplementaryViewSource for the size of a supplementary view for the given kind and index path.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (CGSize)sizeForSupplementaryViewOfKind:(nonnull NSString *)elementKind
    +                                 atIndex:(NSInteger)index;
    + +
    +
    +

    Swift

    +
    func sizeForSupplementaryView(ofKind elementKind: Any!, atIndex index: Any!) -> Any!
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + elementKind + + +
    +

    The kind of supplementary view.

    + +
    +
    + + index + + +
    +

    The index of the requested row.

    + +
    +
    +
    +
    +

    Return Value

    +

    The size for the supplementary view.

    + +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListUpdatingDelegate.html b/docs/Protocols/IGListUpdatingDelegate.html new file mode 100644 index 000000000..ef9708ce2 --- /dev/null +++ b/docs/Protocols/IGListUpdatingDelegate.html @@ -0,0 +1,834 @@ + + + + IGListUpdatingDelegate Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListUpdatingDelegate

+
+
+
@protocol IGListUpdatingDelegate
+ +
+
+

Implement this protocol in order to handle both section and row based update events. Implementation should forward or +coalesce these events to a backing store or collection.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Asks the delegate for the pointer functions for looking up an object in a collection.

    + +

    @discussion Since the updating delegate is responsible for transitioning between object sets, it becomes the source of +truth for how objects and their corresponding section controllers are mapped. This allows the updater to control if +objects are looked up by pointer, or more traditionally, with hash/isEqual.

    + +

    For behavior similar to NSDictionary, simply return ++[NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsObjectPersonality].

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (nonnull NSPointerFunctions *)objectLookupPointerFunctions;
    + +
    +
    +

    Swift

    +
    func objectLookupPointerFunctions() -> Any!
    + +
    +
    +
    +

    Return Value

    +

    Pointer functions for looking up an object in a collection.

    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate to perform a section transition from an old array of objects to a new one.

    + +

    @discussion Implementations determine how to transition between objects. You can perform a diff on the objects, reload +each section, or simply call -reloadData on the collection view. In the end, the collection view must be setup with a +section for each object in the toObjects array.

    + +

    The objectUpdateBlock block should be called prior to making any UICollectionView updates, passing in the toObjects +that the updater is applying.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)
    +performUpdateWithCollectionView:(nonnull UICollectionView *)collectionView
    +                    fromObjects:
    +                        (nullable NSArray<id<IGListDiffable>> *)fromObjects
    +                      toObjects:
    +                          (nullable NSArray<id<IGListDiffable>> *)toObjects
    +                       animated:(BOOL)animated
    +          objectTransitionBlock:
    +              (nonnull IGListObjectTransitionBlock)objectTransitionBlock
    +                     completion:(nullable IGListUpdatingCompletion)completion;
    + +
    +
    +

    Swift

    +
    func performUpdate(withCollectionView collectionView: Any!, fromObjects: Any!, toObjects: Any!, animated: Any!, objectTransitionBlock: IGListObjectTransitionBlock!, completion: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + collectionView + + +
    +

    The collection view to perform the transition on.

    + +
    +
    + + fromObjects + + +
    +

    The previous objects in the collection view. Objects must conform to IGListDiffable.

    + +
    +
    + + toObjects + + +
    +

    The new objects in collection view. Objects must conform to IGListDiffable.

    + +
    +
    + + animated + + +
    +

    A flag indicating if the transition should be animated.

    + +
    +
    + + objectTransitionBlock + + +
    +

    A block that must be called when the adapter applies changes to the collection view.

    + +
    +
    + + completion + + +
    +

    A completion block to execute when the update is finished.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate to perform item inserts at the given index paths.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)insertItemsIntoCollectionView:(nonnull UICollectionView *)collectionView
    +                           indexPaths:
    +                               (nonnull NSArray<NSIndexPath *> *)indexPaths;
    + +
    +
    +

    Swift

    +
    func insertItems(intoCollectionView collectionView: Any!, indexPaths: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + collectionView + + +
    +

    The collection view to perform the transition on.

    + +
    +
    + + indexPaths + + +
    +

    The index paths to insert items into.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate to perform item deletes at the given index paths.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)deleteItemsFromCollectionView:(nonnull UICollectionView *)collectionView
    +                           indexPaths:
    +                               (nonnull NSArray<NSIndexPath *> *)indexPaths;
    + +
    +
    +

    Swift

    +
    func deleteItems(fromCollectionView collectionView: Any!, indexPaths: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + collectionView + + +
    +

    The collection view to perform the transition on.

    + +
    +
    + + indexPaths + + +
    +

    The index paths to delete items from.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Tells the delegate to perform item reloads at the given index paths.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)reloadItemsInCollectionView:(nonnull UICollectionView *)collectionView
    +                         indexPaths:
    +                             (nonnull NSArray<NSIndexPath *> *)indexPaths;
    + +
    +
    +

    Swift

    +
    func reloadItems(inCollectionView collectionView: Any!, indexPaths: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + collectionView + + +
    +

    The collection view to perform the transition on.

    + +
    +
    + + indexPaths + + +
    +

    The index paths of items to reload.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Completely reload data in the collection.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)
    +reloadDataWithCollectionView:(nonnull UICollectionView *)collectionView
    +           reloadUpdateBlock:(nonnull IGListReloadUpdateBlock)reloadUpdateBlock
    +                  completion:(nullable IGListUpdatingCompletion)completion;
    + +
    +
    +

    Swift

    +
    func reloadData(withCollectionView collectionView: Any!, reloadUpdate reloadUpdateBlock: IGListReloadUpdateBlock!, completion: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + +
    + + collectionView + + +
    +

    The collection view to reload.

    + +
    +
    + + reloadUpdateBlock + + +
    +

    A block that must be called when the adapter reloads the collection view.

    + +
    +
    + + completion + + +
    +

    A completion block to execute when the reload is finished.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Completely reload each section in the collection view.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)reloadCollectionView:(nonnull UICollectionView *)collectionView
    +                    sections:(nonnull NSIndexSet *)sections;
    + +
    +
    +

    Swift

    +
    func reloadCollectionView(_ collectionView: Any!, sections: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + collectionView + + +
    +

    The collection view to reload.

    + +
    +
    + + sections + + +
    +

    The sections to reload.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Perform an item update block in the collection view.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)
    +performUpdateWithCollectionView:(nonnull UICollectionView *)collectionView
    +                       animated:(BOOL)animated
    +                    itemUpdates:(nonnull IGListItemUpdateBlock)itemUpdates
    +                     completion:(nullable IGListUpdatingCompletion)completion;
    + +
    +
    +

    Swift

    +
    func performUpdate(withCollectionView collectionView: Any!, animated: Any!, itemUpdates: IGListItemUpdateBlock!, completion: Any!)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + +
    + + collectionView + + +
    +

    The collection view to update.

    + +
    +
    + + animated + + +
    +

    A flag indicating if the transition should be animated.

    + +
    +
    + + itemUpdates + + +
    +

    A block containing all of the updates.

    + +
    +
    + + completion + + +
    +

    A completion block to execute when the update is finished.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Protocols/IGListWorkingRangeDelegate.html b/docs/Protocols/IGListWorkingRangeDelegate.html new file mode 100644 index 000000000..ef94e61cc --- /dev/null +++ b/docs/Protocols/IGListWorkingRangeDelegate.html @@ -0,0 +1,340 @@ + + + + IGListWorkingRangeDelegate Protocol Reference + + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

IGListWorkingRangeDelegate

+
+
+
@protocol IGListWorkingRangeDelegate <NSObject>
+ +
+
+

Implement this protocol to receive working range events for a list.

+ +

The working range is a range near the viewport in which you can begin preparing content for display. For example, +you could begin decoding images, or warming text caches.

+ +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that an section controller will enter the working range.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(nonnull IGListAdapter *)listAdapter
    +    sectionControllerWillEnterWorkingRange:
    +        (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The adapter controlling the feed.

    + +
    +
    + + sectionController + + +
    +

    The section controller entering the range.

    + +
    +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Notifies the delegate that an section controller exited the working range.

    + +
    +
    +

    Declaration

    +
    +

    Objective-C

    +
    - (void)listAdapter:(nonnull IGListAdapter *)listAdapter
    +    sectionControllerDidExitWorkingRange:
    +        (nonnull IGListSectionController<IGListSectionType> *)sectionController;
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + listAdapter + + +
    +

    The adapter controlling the feed.

    + +
    +
    + + sectionController + + +
    +

    The section controller that exited the range.

    + +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + diff --git a/docs/Type Definitions.html b/docs/Type Definitions.html new file mode 100644 index 000000000..a88da8b4b --- /dev/null +++ b/docs/Type Definitions.html @@ -0,0 +1,305 @@ + + + + Type Definitions Reference + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+

Type Definitions

+

The following type definitions are available globally.

+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ + + diff --git a/docs/css/highlight.css b/docs/css/highlight.css new file mode 100644 index 000000000..d0db0e13b --- /dev/null +++ b/docs/css/highlight.css @@ -0,0 +1,200 @@ +/* Credit to https://gist.github.com/wataru420/2048287 */ +.highlight { + /* Comment */ + /* Error */ + /* Keyword */ + /* Operator */ + /* Comment.Multiline */ + /* Comment.Preproc */ + /* Comment.Single */ + /* Comment.Special */ + /* Generic.Deleted */ + /* Generic.Deleted.Specific */ + /* Generic.Emph */ + /* Generic.Error */ + /* Generic.Heading */ + /* Generic.Inserted */ + /* Generic.Inserted.Specific */ + /* Generic.Output */ + /* Generic.Prompt */ + /* Generic.Strong */ + /* Generic.Subheading */ + /* Generic.Traceback */ + /* Keyword.Constant */ + /* Keyword.Declaration */ + /* Keyword.Pseudo */ + /* Keyword.Reserved */ + /* Keyword.Type */ + /* Literal.Number */ + /* Literal.String */ + /* Name.Attribute */ + /* Name.Builtin */ + /* Name.Class */ + /* Name.Constant */ + /* Name.Entity */ + /* Name.Exception */ + /* Name.Function */ + /* Name.Namespace */ + /* Name.Tag */ + /* Name.Variable */ + /* Operator.Word */ + /* Text.Whitespace */ + /* Literal.Number.Float */ + /* Literal.Number.Hex */ + /* Literal.Number.Integer */ + /* Literal.Number.Oct */ + /* Literal.String.Backtick */ + /* Literal.String.Char */ + /* Literal.String.Doc */ + /* Literal.String.Double */ + /* Literal.String.Escape */ + /* Literal.String.Heredoc */ + /* Literal.String.Interpol */ + /* Literal.String.Other */ + /* Literal.String.Regex */ + /* Literal.String.Single */ + /* Literal.String.Symbol */ + /* Name.Builtin.Pseudo */ + /* Name.Variable.Class */ + /* Name.Variable.Global */ + /* Name.Variable.Instance */ + /* Literal.Number.Integer.Long */ } + .highlight .c { + color: #999988; + font-style: italic; } + .highlight .err { + color: #a61717; + background-color: #e3d2d2; } + .highlight .k { + color: #000000; + font-weight: bold; } + .highlight .o { + color: #000000; + font-weight: bold; } + .highlight .cm { + color: #999988; + font-style: italic; } + .highlight .cp { + color: #999999; + font-weight: bold; } + .highlight .c1 { + color: #999988; + font-style: italic; } + .highlight .cs { + color: #999999; + font-weight: bold; + font-style: italic; } + .highlight .gd { + color: #000000; + background-color: #ffdddd; } + .highlight .gd .x { + color: #000000; + background-color: #ffaaaa; } + .highlight .ge { + color: #000000; + font-style: italic; } + .highlight .gr { + color: #aa0000; } + .highlight .gh { + color: #999999; } + .highlight .gi { + color: #000000; + background-color: #ddffdd; } + .highlight .gi .x { + color: #000000; + background-color: #aaffaa; } + .highlight .go { + color: #888888; } + .highlight .gp { + color: #555555; } + .highlight .gs { + font-weight: bold; } + .highlight .gu { + color: #aaaaaa; } + .highlight .gt { + color: #aa0000; } + .highlight .kc { + color: #000000; + font-weight: bold; } + .highlight .kd { + color: #000000; + font-weight: bold; } + .highlight .kp { + color: #000000; + font-weight: bold; } + .highlight .kr { + color: #000000; + font-weight: bold; } + .highlight .kt { + color: #445588; } + .highlight .m { + color: #009999; } + .highlight .s { + color: #d14; } + .highlight .na { + color: #008080; } + .highlight .nb { + color: #0086B3; } + .highlight .nc { + color: #445588; + font-weight: bold; } + .highlight .no { + color: #008080; } + .highlight .ni { + color: #800080; } + .highlight .ne { + color: #990000; + font-weight: bold; } + .highlight .nf { + color: #990000; } + .highlight .nn { + color: #555555; } + .highlight .nt { + color: #000080; } + .highlight .nv { + color: #008080; } + .highlight .ow { + color: #000000; + font-weight: bold; } + .highlight .w { + color: #bbbbbb; } + .highlight .mf { + color: #009999; } + .highlight .mh { + color: #009999; } + .highlight .mi { + color: #009999; } + .highlight .mo { + color: #009999; } + .highlight .sb { + color: #d14; } + .highlight .sc { + color: #d14; } + .highlight .sd { + color: #d14; } + .highlight .s2 { + color: #d14; } + .highlight .se { + color: #d14; } + .highlight .sh { + color: #d14; } + .highlight .si { + color: #d14; } + .highlight .sx { + color: #d14; } + .highlight .sr { + color: #009926; } + .highlight .s1 { + color: #d14; } + .highlight .ss { + color: #990073; } + .highlight .bp { + color: #999999; } + .highlight .vc { + color: #008080; } + .highlight .vg { + color: #008080; } + .highlight .vi { + color: #008080; } + .highlight .il { + color: #009999; } diff --git a/docs/css/jazzy.css b/docs/css/jazzy.css new file mode 100644 index 000000000..d6d65b7f0 --- /dev/null +++ b/docs/css/jazzy.css @@ -0,0 +1,332 @@ +html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { + background: transparent; + border: 0; + margin: 0; + outline: 0; + padding: 0; + vertical-align: baseline; } + +body { + background-color: #f2f2f2; + font-family: Helvetica, freesans, Arial, sans-serif; + font-size: 14px; + -webkit-font-smoothing: subpixel-antialiased; + word-wrap: break-word; } + +h1, h2, h3 { + margin-top: 0.8em; + margin-bottom: 0.3em; + font-weight: 100; + color: black; } + +h1 { + font-size: 2.5em; } + +h2 { + font-size: 2em; + border-bottom: 1px solid #e2e2e2; } + +h4 { + font-size: 13px; + line-height: 1.5; + margin-top: 21px; } + +h5 { + font-size: 1.1em; } + +h6 { + font-size: 1.1em; + color: #777; } + +.section-name { + color: gray; + display: block; + font-family: Helvetica; + font-size: 22px; + font-weight: 100; + margin-bottom: 15px; } + +pre, code { + font: 0.95em Menlo, monospace; + color: #777; + word-wrap: normal; } + +p code, li code { + background-color: #eee; + padding: 2px 4px; + border-radius: 4px; } + +a { + color: #0088cc; + text-decoration: none; } + +ul { + padding-left: 15px; } + +li { + line-height: 1.8em; } + +img { + max-width: 100%; } + +blockquote { + margin-left: 0; + padding: 0 10px; + border-left: 4px solid #ccc; } + +.content-wrapper { + margin: 0 auto; + width: 980px; } + +header { + font-size: 0.85em; + line-height: 26px; + background-color: #414141; + position: fixed; + width: 100%; + z-index: 1; } + header img { + padding-right: 6px; + vertical-align: -4px; + height: 16px; } + header a { + color: #fff; } + header p { + float: left; + color: #999; } + header .header-right { + float: right; + margin-left: 16px; } + +#breadcrumbs { + background-color: #f2f2f2; + height: 27px; + padding-top: 17px; + position: fixed; + width: 100%; + z-index: 1; + margin-top: 26px; } + #breadcrumbs #carat { + height: 10px; + margin: 0 5px; } + +.sidebar { + background-color: #f9f9f9; + border: 1px solid #e2e2e2; + overflow-y: auto; + overflow-x: hidden; + position: fixed; + top: 70px; + bottom: 0; + width: 230px; + word-wrap: normal; } + +.nav-groups { + list-style-type: none; + background: #fff; + padding-left: 0; } + +.nav-group-name { + border-bottom: 1px solid #e2e2e2; + font-size: 1.1em; + font-weight: 100; + padding: 15px 0 15px 20px; } + .nav-group-name > a { + color: #333; } + +.nav-group-tasks { + margin-top: 5px; } + +.nav-group-task { + font-size: 0.9em; + list-style-type: none; + white-space: nowrap; } + .nav-group-task a { + color: #888; } + +.main-content { + background-color: #fff; + border: 1px solid #e2e2e2; + margin-left: 246px; + position: absolute; + overflow: hidden; + padding-bottom: 60px; + top: 70px; + width: 734px; } + .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { + margin-bottom: 1em; } + .main-content p { + line-height: 1.8em; } + .main-content section .section:first-child { + margin-top: 0; + padding-top: 0; } + .main-content section .task-group-section .task-group:first-of-type { + padding-top: 10px; } + .main-content section .task-group-section .task-group:first-of-type .section-name { + padding-top: 15px; } + +.section { + padding: 0 25px; } + +.highlight { + background-color: #eee; + padding: 10px 12px; + border: 1px solid #e2e2e2; + border-radius: 4px; + overflow-x: auto; } + +.declaration .highlight { + overflow-x: initial; + padding: 0 40px 40px 0; + margin-bottom: -25px; + background-color: transparent; + border: none; } + +.section-name { + margin: 0; + margin-left: 18px; } + +.task-group-section { + padding-left: 6px; + border-top: 1px solid #e2e2e2; } + +.task-group { + padding-top: 0px; } + +.task-name-container a[name]:before { + content: ""; + display: block; + padding-top: 70px; + margin: -70px 0 0; } + +.item { + padding-top: 8px; + width: 100%; + list-style-type: none; } + .item a[name]:before { + content: ""; + display: block; + padding-top: 70px; + margin: -70px 0 0; } + .item code { + background-color: transparent; + padding: 0; } + .item .token { + padding-left: 3px; + margin-left: 15px; + font-size: 11.9px; } + .item .declaration-note { + font-size: .85em; + color: gray; + font-style: italic; } + +.pointer-container { + border-bottom: 1px solid #e2e2e2; + left: -23px; + padding-bottom: 13px; + position: relative; + width: 110%; } + +.pointer { + background: #f9f9f9; + border-left: 1px solid #e2e2e2; + border-top: 1px solid #e2e2e2; + height: 12px; + left: 21px; + top: -7px; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + position: absolute; + width: 12px; } + +.height-container { + display: none; + left: -25px; + padding: 0 25px; + position: relative; + width: 100%; + overflow: hidden; } + .height-container .section { + background: #f9f9f9; + border-bottom: 1px solid #e2e2e2; + left: -25px; + position: relative; + width: 100%; + padding-top: 10px; + padding-bottom: 5px; } + +.aside, .language { + padding: 6px 12px; + margin: 12px 0; + border-left: 5px solid #dddddd; + overflow-y: hidden; } + .aside .aside-title, .language .aside-title { + font-size: 9px; + letter-spacing: 2px; + text-transform: uppercase; + padding-bottom: 0; + margin: 0; + color: #aaa; + -webkit-user-select: none; } + .aside p:last-child, .language p:last-child { + margin-bottom: 0; } + +.language { + border-left: 5px solid #cde9f4; } + .language .aside-title { + color: #4b8afb; } + +.aside-warning { + border-left: 5px solid #ff6666; } + .aside-warning .aside-title { + color: #ff0000; } + +.graybox { + border-collapse: collapse; + width: 100%; } + .graybox p { + margin: 0; + word-break: break-word; + min-width: 50px; } + .graybox td { + border: 1px solid #e2e2e2; + padding: 5px 25px 5px 10px; + vertical-align: middle; } + .graybox tr td:first-of-type { + text-align: right; + padding: 7px; + vertical-align: top; + word-break: normal; + width: 40px; } + +.slightly-smaller { + font-size: 0.9em; } + +#footer { + position: absolute; + bottom: 10px; + margin-left: 25px; } + #footer p { + margin: 0; + color: #aaa; + font-size: 0.8em; } + +html.dash header, html.dash #breadcrumbs, html.dash .sidebar { + display: none; } +html.dash .main-content { + width: 980px; + margin-left: 0; + border: none; + width: 100%; + top: 0; + padding-bottom: 0; } +html.dash .height-container { + display: block; } +html.dash .item .token { + margin-left: 0; } +html.dash .content-wrapper { + width: auto; } +html.dash #footer { + position: static; } diff --git a/docs/img/carat.png b/docs/img/carat.png new file mode 100755 index 000000000..29d2f7fd4 Binary files /dev/null and b/docs/img/carat.png differ diff --git a/docs/img/dash.png b/docs/img/dash.png new file mode 100755 index 000000000..6f694c7a0 Binary files /dev/null and b/docs/img/dash.png differ diff --git a/docs/img/gh.png b/docs/img/gh.png new file mode 100755 index 000000000..628da97c7 Binary files /dev/null and b/docs/img/gh.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..c1cd7a753 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,411 @@ + + + + IGListKit Reference + + + + + + + + + +
+
+

IGListKit Docs (79% documented)

+

View on GitHub

+
+
+
+ +
+
+ +
+
+
+ +

+ +

+ + + +
+ +

A data-driven UICollectionView framework for building fast and flexible lists.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Main Features
:no_good:Never call performBatchUpdates(_:, completion:) or reloadData() again
:house:Better architecture with reusable cells and components
:capital_abcd:Create collections with multiple data types
:mag:Customize your diffing behavior for your models
:white_check_mark:Fully unit tested
:iphone:Simply UICollectionView at its core
:rocket:Extendable updating API
:key:Decoupled diffing algorithm
:bulb:Display and near-display delegate events
+ +

IGListKit powers every major product in Instagram for hundreds of millions of users. It was built to be fast, efficient, and stable.

+

Installation

+ +

The preferred installation method for IGListKit is with Cocoapods. Simply add the following to your Podfile:

+
# Latest release of IGListKit
+pod 'IGListKit'
+
+ +

You can also manually install the framework by dragging and dropping the IGListKit.xcodeproj into your workspace.

+ +

IGListKit supports a minimum iOS version of 8.0.

+

Creating your first list

+ +

After installing IGListKit, creating a new list is really simple.

+

Creating a section controller

+ +

Creating a new section controller is very simple. You just subclass IGListSectionController and conform to the IGListSectionType protocol. Once you conform to IGListSectionType, the compiler will make sure you implement all of the required methods.

+ +

Take a look at LabelSectionController for an example section controller that handles a String and configures a single cell with a UILabel.

+
class LabelSectionController: IGListSectionController, IGListSectionType {
+  // ...
+}
+
+

Creating the UI

+ +

After creating at least one section controller, you must create an IGListCollectionView and IGListAdapter.

+
let layout = UICollectionViewFlowLayout()
+let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: layout)
+
+let updater = IGListAdapterUpdater()
+let adapter = IGListAdapter(updatingDelegate: updater, viewController: self, workingRangeSize: 0)
+adapter.collectionView = collectionView
+
+ +
+

Note: This example is done within a UIViewController and uses both a stock UICollectionViewFlowLayout and IGListAdapterUpdater. You can use your own layout and updater if you need advanced features!

+
+

Connecting a data source

+ +

The last step is the IGListAdapter’s data source and returning some data.

+
func objectssForListAdapter(listAdapter: IGListAdapter) -> [IGListDiffable] {
+  // this can be anything!
+  return [ "Foo", "Bar", 42, "Biz" ]
+}
+
+func listAdapter(listAdapter: IGListAdapter,
+    sectionControllerForObject(object: AnyObject) -> IGListSectionController {
+  if let _ = object as? String {
+    return LabelSectionController()
+  } else {
+    return NumberSectionController()
+  }
+}
+
+func emptyViewForListAdapter(listAdapter: IGListAdapter) -> UIView? {
+  return nil
+}
+
+ +

You can return an array of any type of data, as long as it conforms to IGListDiffable. We’ve included a default implementation for all objects, but adding your own implementation can unlock even better diffing.

+

Diffing

+ +

IGListKit uses an algorithm adapted from a paper titled A technique for isolating differences between files by Paul Heckel. This algorithm uses a technique known as the longest common subsequence to find a minimal diff between collections in linear time O(n). It finds all inserts, deletes, updates, and moves between arrays of data.

+ +

To add custom, diffable models, you need to conform to the IGListDiffable protocol and implement diffIdentifier() and isEqual(_:).

+ +

For an example, consider the following model:

+
class User {
+  let primaryKey: Int
+  let name: String
+  // implementation, etc
+}
+
+ +

The user’s primaryKey uniquely identifies user data, and the name is just the value for that user.

+ +

Consider the following two users:

+
let jack = User(primaryKey: 2, name: "Jack")
+let jill = User(primaryKey: 2, name: "Jill")
+
+ +

Both jack and jill represent the same unique data because they share the same primaryKey, but they are not equal because their names are different.

+ +

To represent this in IGListKit’s diffing, add and implement the IGListDiffable protocol:

+
extension User: IGListDiffable {
+  func diffIdentifier() -> NSObjectProtocol {
+    return pk
+  }
+
+  func isEqual(object: AnyObject?) -> Bool {
+    if object === self {
+      return true
+    }
+    if let object = object as? User {
+      return name == object.name
+    }
+    return false
+  }
+}
+
+ +

The algorithm will skip updating two User objects that have the same primaryKey and name, even if they are different instances! You now avoid unecessary UI updates in the collection view even when providing new instances.

+ +
+

Note: Remember that isEqual(_:) should return false when you want to reload the cells in the corresponding section controller.

+
+

Advanced Features

+

Delegates

+ +

Supplementary Views

+ +

Adding supplementary views to section controllers is as simple as setting the weak supplementaryViewSource and implementing the IGListSupplementaryViewSource protocol. This protocol works nearly the same as returning and configuring cells.

+ +

Display Delegate

+ +

Section controllers can set the weak displayDelegate delegate to an object, including self, to receive display events about an section controller and individual cells.

+ +

Working Range

+ +

A working range is a distance before and after the visible bounds of the UICollectionView where section controllers within this bounds are notified of their entrance and exit. This concept lets your section controllers prepare content before they come on screen (e.g. download images).

+ +

The IGListAdapter must be initialized with a range value in order to work. This value is a multiple of the visible height or width, depending on the scroll-direction.

+
let adapter = IGListAdapter(updatingDelegate: IGListAdapterUpdater(),
+                              viewController: self,
+                            workingRangeSize: 0.5) // 0.5x the visible size
+
+ +

working-range

+ +

You can set the weak workingRangeDelegate on an section controller to receive events.

+

Custom Updaters

+ +

The default IGListAdapterUpdater should handle any UICollectionView update that you need. However, if you find the functionality lacking, or want to perform updates in a very specific way, you can create an object that conforms to the IGListUpdatingDelegate protocol and initialize a new IGListAdapter with it.

+ +

Check out the updater IGListReloadDataUpdater used in unit tests for an example.

+

Experiments

+ +

IGListKit comes with the ability to add experimental (or prerelease) features. If an enhancement or fix cannot be proven with a unit test, we encourage you to wrap changes in an experiment so that it can be properly tested in production.

+

Documentation

+ +

Read the docs here. Documentation is generated with jazzy and hosted on GitHub-Pages.

+

Generating docs

+
$ ./build_docs.sh
+$ open -a safari docs/index.html  # preview in Safari
+
+

Contributing

+ +

See the CONTRIBUTING file for how to help out.

+

License

+ +

IGListKit is BSD-licensed. We also provide an additional patent grant.

+ +

The files in the /Example directory are licensed under a separate license as specified in each file; documentation is licensed CC-BY-4.0.

+ +
+
+ +
+
+ + + diff --git a/docs/js/jazzy.js b/docs/js/jazzy.js new file mode 100755 index 000000000..4ff9455b6 --- /dev/null +++ b/docs/js/jazzy.js @@ -0,0 +1,40 @@ +window.jazzy = {'docset': false} +if (typeof window.dash != 'undefined') { + document.documentElement.className += ' dash' + window.jazzy.docset = true +} +if (navigator.userAgent.match(/xcode/i)) { + document.documentElement.className += ' xcode' + window.jazzy.docset = true +} + +// On doc load, toggle the URL hash discussion if present +$(document).ready(function() { + if (!window.jazzy.docset) { + var linkToHash = $('a[href="' + window.location.hash +'"]'); + linkToHash.trigger("click"); + } +}); + +// On token click, toggle its discussion and animate token.marginLeft +$(".token").click(function(event) { + if (window.jazzy.docset) { + return; + } + var link = $(this); + var animationDuration = 300; + var tokenOffset = "15px"; + var original = link.css('marginLeft') == tokenOffset; + link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); + $content = link.parent().parent().next(); + $content.slideToggle(animationDuration); + + // Keeps the document from jumping to the hash. + var href = $(this).attr('href'); + if (history.pushState) { + history.pushState({}, '', href); + } else { + location.hash = href; + } + event.preventDefault(); +}); diff --git a/docs/js/jquery.min.js b/docs/js/jquery.min.js new file mode 100755 index 000000000..ab28a2472 --- /dev/null +++ b/docs/js/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; +if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("