commit 3e216e1aa4599ee93d40be0d2f90146ccc67cb8b Author: Ryo Aoyama Date: Mon Jun 24 05:49:24 2019 +0900 Initial commit diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md new file mode 100644 index 0000000..66e3b61 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -0,0 +1,42 @@ +--- +name: Bug Report +about: Create a bug report. +--- + +## Checklist +- [ ] This is not a Apple's bug. +- [ ] Reviewed the README and documents. +- [ ] Searched existing issues for ensure not duplicated. + +## Expected Behavior + + +## Current Behavior + + +## Steps to Reproduce + + +1. +2. +3. +4. + +## Detailed Description (Include Screenshots) + + +## Reproducible Demo Project + + +## Environments +- version: + +- Swift version: + +- iOS version: + +- Xcode version: + +- Devices/Simulators: + +- CocoaPods/Carthage version: diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md new file mode 100644 index 0000000..f404bfc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md @@ -0,0 +1,18 @@ +--- +name: Feature Request +about: Create a feature request. +--- + +## Checklist +- [ ] Reviewed the README and documents. +- [ ] Searched existing issues for ensure not duplicated. + +## Description + + +## Motivation and Context + + + +## Proposed Solution + diff --git a/.github/ISSUE_TEMPLATE/QUESTION.md b/.github/ISSUE_TEMPLATE/QUESTION.md new file mode 100644 index 0000000..35de998 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/QUESTION.md @@ -0,0 +1,30 @@ +--- +name: Question +about: Create a bug question. +--- + +## Checklist +- [ ] Reviewed the README and documents. +- [ ] Searched existing issues for ensure not duplicated. + +## Expected Behavior + + +## Current Behavior + + +## Detailed Description (Include Screenshots) + + +## Environment +- version: + +- Swift version: + +- iOS version: + +- Xcode version: + +- Devices/Simulators: + +- CocoaPods/Carthage version: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e470dd2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +## Checklist +- [ ] All tests are passed. +- [ ] Added tests. +- [ ] Documented the code using [Xcode markup](https://developer.apple.com/library/mac/documentation/Xcode/Reference/xcode_markup_formatting_ref). +- [ ] Searched existing pull requests for ensure not duplicated. + +## Description + + +## Related Issue + + + + + +## Motivation and Context + + + +## Impact on Existing Code + + +## Screenshots (if appropriate) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb43607 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +.DS_Store +*/build/* +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +xcbaselines +profile +*.moved-aside +DerivedData +.idea/ +*.hmap +*.xccheckout +*.xcuserstate +build/ + +## Documentation +docs/docsets/ +docs/undocumented.json + +## Gems +.bundle +vendor + +## CocoaPods +Pods + +## Carthage +Carthage/* +!Carthage/Checkouts + +## Swift Package Manager +.build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bd6ba15 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Carthage/Checkouts/DifferenceKit"] + path = Carthage/Checkouts/DifferenceKit + url = https://github.com/ra1028/DifferenceKit.git diff --git a/.jazzy.yml b/.jazzy.yml new file mode 100644 index 0000000..74bf4d2 --- /dev/null +++ b/.jazzy.yml @@ -0,0 +1,30 @@ +# https://github.com/realm/jazzy + +author: Ryo Aoyama +author_url: https://github.com/ra1028 +github_url: https://github.com/ra1028/DiffableDataSources +module: DiffableDataSources +readme: README.md +output: docs +theme: fullwidth +clean: true +skip_undocumented: true +xcodebuild_arguments: + - -workspace + - DiffableDataSources.xcworkspace + - -scheme + - DiffableDataSources + - -sdk + - iphonesimulator + +exclude: + - Sources/AppKit/CocoaCollectionViewDiffableDataSource.swift + +custom_categories: + - name: DataSources + children: + - TableViewDiffableDataSource + - CollectionViewDiffableDataSource + - name: Snapshot + children: + - DiffableDataSourceSnapshot diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..819e07a --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +5.0 diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..6cb2218 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,34 @@ +# https://github.com/realm/SwiftLint + +excluded: + - .build + - Carthage + - Examples + +disabled_rules: + - type_name + - identifier_name + - generic_type_name + - force_cast + +nesting: + type_level: + warning: 2 + +line_length: + warning: 200 + +file_length: + warning: 600 + +type_body_length: + warning: 400 + +function_body_length: + warning: 50 + +cyclomatic_complexity: + warning: 12 + +statement_position: + statement_mode: uncuddled_else diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c51e93f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..84b73f6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,91 @@ +# 🎈 Contributing + +First of all, thanks for your interest. + +There are several ways to contribute to this project. We welcome contributions in all ways. +We have made some contribution guidelines to smoothly incorporate your opinions and code into this project. + +## 📝 Open Issue + +When you found a bug or having a feature request, search for the issue from the existing issues and feel free to open the issue after making sure it isn't already reported. + +In order to we understand your issue accurately, please include as much information as possible in the issue template. +The screenshot are also big clue to understand the issue. + +If you know exactly how to fix the bug you report or implement the feature you propose, please pull request instead of an issue. + +## 🚀 Pull Request + +We are waiting for a pull request to make this project more better with us. +If you want to add a new feature, let's discuss about it first on issue. + +### Lint + +Please introduce [SwiftLint](https://github.com/realm/SwiftLint) into your environment before start writing the code. +Xcode automatically runs lint in the build phase. + +The code written according to lint should match our coding style, but for particular cases where style is unknown, refer to the existing code base. + +### Test + +The test will tells us the validity of your code. +All codes entering the master must pass the all tests. +If you change the code or add new features, you should add tests. + +### Documentation + +Please write the document using [Xcode markup](https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_markup_formatting_ref/) to the code you added. +Documentation template is inserted automatically by using Xcode shortcut **⌥⌘/**. +Our document style is slightly different from the template. The example is below. + +```swift +/// The example class for documentation. +final class Foo { + /// A property value. + let prop: Int + + /// Create a new foo with a param. + /// + /// - Parameters: + /// - param: An Int value for prop. + init(param: Int) { + prop = param + } + + /// Returns a string value concatenating `param1` and `param2`. + /// + /// - Parameters: + /// - param1: An Int value for prefix. + /// - param2: A String value for suffix. + /// + /// - Returns: A string concatenating given params. + func bar(param1: Int, param2: String) -> String { + return "\(param1)" + param2 + } +} +``` + +## [Developer's Certificate of Origin 1.1](https://elinux.org/Developer_Certificate_Of_Origin) +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/Cartfile b/Cartfile new file mode 100644 index 0000000..02c9537 --- /dev/null +++ b/Cartfile @@ -0,0 +1 @@ +github "ra1028/DifferenceKit" ~> 1.1 diff --git a/Cartfile.resolved b/Cartfile.resolved new file mode 100644 index 0000000..d98f6bc --- /dev/null +++ b/Cartfile.resolved @@ -0,0 +1 @@ +github "ra1028/DifferenceKit" "1.1.3" diff --git a/Carthage/Checkouts/DifferenceKit b/Carthage/Checkouts/DifferenceKit new file mode 160000 index 0000000..4eb31f8 --- /dev/null +++ b/Carthage/Checkouts/DifferenceKit @@ -0,0 +1 @@ +Subproject commit 4eb31f8e85e4cb13732f9664d6e01e507cd592a0 diff --git a/DiffableDataSources.podspec b/DiffableDataSources.podspec new file mode 100644 index 0000000..6501981 --- /dev/null +++ b/DiffableDataSources.podspec @@ -0,0 +1,27 @@ +Pod::Spec.new do |spec| + spec.name = 'DiffableDataSources' + spec.version = '0.1.0' + spec.author = { 'ra1028' => 'r.fe51028.r@gmail.com' } + spec.homepage = 'https://github.com/ra1028/DiffableDataSources' + spec.documentation_url = 'https://ra1028.github.io/DiffableDataSources' + spec.summary = 'A library for backporting UITableView/UICollectionViewDiffableDataSource.' + spec.source = { :git => 'https://github.com/ra1028/DiffableDataSources.git', :tag => spec.version.to_s } + spec.license = { :type => 'Apache 2.0', :file => 'LICENSE' } + spec.requires_arc = true + spec.source_files = 'Sources/**/*.swift' + spec.swift_versions = ["5.0", "5.1"] + + differenekit_version = '~> 1.1' + + spec.ios.dependency 'DifferenceKit/UIKitExtension', differenekit_version + spec.tvos.dependency 'DifferenceKit/UIKitExtension', differenekit_version + spec.osx.dependency 'DifferenceKit/AppKitExtension', differenekit_version + + spec.ios.frameworks = 'UIKit' + spec.tvos.frameworks = 'UIKit' + spec.osx.frameworks = 'Appkit' + + spec.ios.deployment_target = '9.0' + spec.tvos.deployment_target = '9.0' + spec.osx.deployment_target = '10.11' +end diff --git a/DiffableDataSources.xcodeproj/project.pbxproj b/DiffableDataSources.xcodeproj/project.pbxproj new file mode 100644 index 0000000..2241b59 --- /dev/null +++ b/DiffableDataSources.xcodeproj/project.pbxproj @@ -0,0 +1,591 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 6B41587222B5210500183AA2 /* TableViewDiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B41587122B5210500183AA2 /* TableViewDiffableDataSource.swift */; }; + 6B46555C22B6BC81005202D1 /* HashableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B46555B22B6BC81005202D1 /* HashableExtension.swift */; }; + 6B46555E22B7F904005202D1 /* UniversalError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B46555D22B7F904005202D1 /* UniversalError.swift */; }; + 6B466B2222B969600035D72F /* CollectionViewDiffableDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B466B2122B969600035D72F /* CollectionViewDiffableDataSourceTests.swift */; }; + 6B466B2422B96A520035D72F /* CocoaCollectionViewDiffableDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B466B2322B96A520035D72F /* CocoaCollectionViewDiffableDataSourceTests.swift */; }; + 6BA3A78022B54DA400AD51ED /* CollectionViewDiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA3A77F22B54DA400AD51ED /* CollectionViewDiffableDataSource.swift */; }; + 6BA3A79422B5A35F00AD51ED /* DiffableDataSourceCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA3A79322B5A35F00AD51ED /* DiffableDataSourceCore.swift */; }; + 6BA3A7B122B6A5CE00AD51ED /* CocoaCollectionViewDiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA3A7B022B6A5CE00AD51ED /* CocoaCollectionViewDiffableDataSource.swift */; }; + 6BA3A7B622B820DA00AD51ED /* TableViewDiffableDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA3A7B522B820DA00AD51ED /* TableViewDiffableDataSourceTests.swift */; }; + 6BA4F52922BC0768008D5117 /* MainThreadSerialDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA4F52822BC0768008D5117 /* MainThreadSerialDispatcher.swift */; }; + 6BA4F52B22BC0AF3008D5117 /* MainThreadSerialDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA4F52A22BC0AF3008D5117 /* MainThreadSerialDispatcherTests.swift */; }; + 6BB762D622B455170050DC03 /* DiffableDataSources.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BB762CC22B455170050DC03 /* DiffableDataSources.framework */; }; + 6BB762EB22B455520050DC03 /* SnapshotStructure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB762E822B455520050DC03 /* SnapshotStructure.swift */; }; + 6BB762EC22B455520050DC03 /* DiffableDataSourceSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB762E922B455520050DC03 /* DiffableDataSourceSnapshot.swift */; }; + 6BB762F122B455590050DC03 /* DiffableDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB762EF22B455590050DC03 /* DiffableDataSourceTests.swift */; }; + 6BB762F722B53C1C0050DC03 /* DifferenceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BB762F622B53C1C0050DC03 /* DifferenceKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6BB762D722B455170050DC03 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6BB762C322B455170050DC03 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6BB762CB22B455170050DC03; + remoteInfo = DiffableDataSources; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 6B41587122B5210500183AA2 /* TableViewDiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDiffableDataSource.swift; sourceTree = ""; }; + 6B46555B22B6BC81005202D1 /* HashableExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashableExtension.swift; sourceTree = ""; }; + 6B46555D22B7F904005202D1 /* UniversalError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalError.swift; sourceTree = ""; }; + 6B466B2122B969600035D72F /* CollectionViewDiffableDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewDiffableDataSourceTests.swift; sourceTree = ""; }; + 6B466B2322B96A520035D72F /* CocoaCollectionViewDiffableDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaCollectionViewDiffableDataSourceTests.swift; sourceTree = ""; }; + 6BA3A77F22B54DA400AD51ED /* CollectionViewDiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewDiffableDataSource.swift; sourceTree = ""; }; + 6BA3A79322B5A35F00AD51ED /* DiffableDataSourceCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSourceCore.swift; sourceTree = ""; }; + 6BA3A7B022B6A5CE00AD51ED /* CocoaCollectionViewDiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaCollectionViewDiffableDataSource.swift; sourceTree = ""; }; + 6BA3A7B522B820DA00AD51ED /* TableViewDiffableDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDiffableDataSourceTests.swift; sourceTree = ""; }; + 6BA4F52822BC0768008D5117 /* MainThreadSerialDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainThreadSerialDispatcher.swift; sourceTree = ""; }; + 6BA4F52A22BC0AF3008D5117 /* MainThreadSerialDispatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainThreadSerialDispatcherTests.swift; sourceTree = ""; }; + 6BB762CC22B455170050DC03 /* DiffableDataSources.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DiffableDataSources.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6BB762D522B455170050DC03 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6BB762E822B455520050DC03 /* SnapshotStructure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotStructure.swift; sourceTree = ""; }; + 6BB762E922B455520050DC03 /* DiffableDataSourceSnapshot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffableDataSourceSnapshot.swift; sourceTree = ""; }; + 6BB762EA22B455520050DC03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6BB762EF22B455590050DC03 /* DiffableDataSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffableDataSourceTests.swift; sourceTree = ""; }; + 6BB762F022B455590050DC03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6BB762F422B455610050DC03 /* DiffableDataSources.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DiffableDataSources.xcconfig; sourceTree = ""; }; + 6BB762F622B53C1C0050DC03 /* DifferenceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DifferenceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6BB762C922B455170050DC03 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BB762F722B53C1C0050DC03 /* DifferenceKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6BB762D222B455170050DC03 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BB762D622B455170050DC03 /* DiffableDataSources.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6B41587322B52BC100183AA2 /* UIKit */ = { + isa = PBXGroup; + children = ( + 6B41587122B5210500183AA2 /* TableViewDiffableDataSource.swift */, + 6BA3A77F22B54DA400AD51ED /* CollectionViewDiffableDataSource.swift */, + ); + path = UIKit; + sourceTree = ""; + }; + 6BA3A7AF22B6A5A200AD51ED /* AppKit */ = { + isa = PBXGroup; + children = ( + 6BA3A7B022B6A5CE00AD51ED /* CocoaCollectionViewDiffableDataSource.swift */, + ); + path = AppKit; + sourceTree = ""; + }; + 6BB762C222B455170050DC03 = { + isa = PBXGroup; + children = ( + 6BB762E622B455520050DC03 /* Sources */, + 6BB762EE22B455590050DC03 /* Tests */, + 6BB762F322B455610050DC03 /* XCConfigs */, + 6BB762CD22B455170050DC03 /* Products */, + 6BB762F522B53C1C0050DC03 /* Frameworks */, + ); + sourceTree = ""; + }; + 6BB762CD22B455170050DC03 /* Products */ = { + isa = PBXGroup; + children = ( + 6BB762CC22B455170050DC03 /* DiffableDataSources.framework */, + 6BB762D522B455170050DC03 /* Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 6BB762E622B455520050DC03 /* Sources */ = { + isa = PBXGroup; + children = ( + 6BB762E922B455520050DC03 /* DiffableDataSourceSnapshot.swift */, + 6B41587322B52BC100183AA2 /* UIKit */, + 6BA3A7AF22B6A5A200AD51ED /* AppKit */, + 6BB762E722B455520050DC03 /* Internal */, + 6BB762EA22B455520050DC03 /* Info.plist */, + ); + path = Sources; + sourceTree = ""; + }; + 6BB762E722B455520050DC03 /* Internal */ = { + isa = PBXGroup; + children = ( + 6BA3A79322B5A35F00AD51ED /* DiffableDataSourceCore.swift */, + 6BB762E822B455520050DC03 /* SnapshotStructure.swift */, + 6BA4F52822BC0768008D5117 /* MainThreadSerialDispatcher.swift */, + 6B46555B22B6BC81005202D1 /* HashableExtension.swift */, + 6B46555D22B7F904005202D1 /* UniversalError.swift */, + ); + path = Internal; + sourceTree = ""; + }; + 6BB762EE22B455590050DC03 /* Tests */ = { + isa = PBXGroup; + children = ( + 6BB762EF22B455590050DC03 /* DiffableDataSourceTests.swift */, + 6BA3A7B522B820DA00AD51ED /* TableViewDiffableDataSourceTests.swift */, + 6B466B2122B969600035D72F /* CollectionViewDiffableDataSourceTests.swift */, + 6B466B2322B96A520035D72F /* CocoaCollectionViewDiffableDataSourceTests.swift */, + 6BA4F52A22BC0AF3008D5117 /* MainThreadSerialDispatcherTests.swift */, + 6BB762F022B455590050DC03 /* Info.plist */, + ); + path = Tests; + sourceTree = ""; + }; + 6BB762F322B455610050DC03 /* XCConfigs */ = { + isa = PBXGroup; + children = ( + 6BB762F422B455610050DC03 /* DiffableDataSources.xcconfig */, + ); + path = XCConfigs; + sourceTree = ""; + }; + 6BB762F522B53C1C0050DC03 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6BB762F622B53C1C0050DC03 /* DifferenceKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 6BB762C722B455170050DC03 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 6BB762CB22B455170050DC03 /* DiffableDataSources */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6BB762E022B455170050DC03 /* Build configuration list for PBXNativeTarget "DiffableDataSources" */; + buildPhases = ( + 6BB762C722B455170050DC03 /* Headers */, + 6BB762C822B455170050DC03 /* Sources */, + 6BB762C922B455170050DC03 /* Frameworks */, + 6BB762CA22B455170050DC03 /* Resources */, + 6BA3A7B222B6C94100AD51ED /* SwiftLint */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DiffableDataSources; + productName = DiffableDataSources; + productReference = 6BB762CC22B455170050DC03 /* DiffableDataSources.framework */; + productType = "com.apple.product-type.framework"; + }; + 6BB762D422B455170050DC03 /* Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6BB762E322B455170050DC03 /* Build configuration list for PBXNativeTarget "Tests" */; + buildPhases = ( + 6BB762D122B455170050DC03 /* Sources */, + 6BB762D222B455170050DC03 /* Frameworks */, + 6BB762D322B455170050DC03 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 6BB762D822B455170050DC03 /* PBXTargetDependency */, + ); + name = Tests; + productName = DiffableDataSourcesTests; + productReference = 6BB762D522B455170050DC03 /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6BB762C322B455170050DC03 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1020; + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "Ryo Aoyama"; + TargetAttributes = { + 6BB762CB22B455170050DC03 = { + CreatedOnToolsVersion = 10.2.1; + }; + 6BB762D422B455170050DC03 = { + CreatedOnToolsVersion = 10.2.1; + }; + }; + }; + buildConfigurationList = 6BB762C622B455170050DC03 /* Build configuration list for PBXProject "DiffableDataSources" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6BB762C222B455170050DC03; + productRefGroup = 6BB762CD22B455170050DC03 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6BB762CB22B455170050DC03 /* DiffableDataSources */, + 6BB762D422B455170050DC03 /* Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6BB762CA22B455170050DC03 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6BB762D322B455170050DC03 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6BA3A7B222B6C94100AD51ED /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint --config .swiftlint.yml\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6BB762C822B455170050DC03 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BA4F52922BC0768008D5117 /* MainThreadSerialDispatcher.swift in Sources */, + 6B41587222B5210500183AA2 /* TableViewDiffableDataSource.swift in Sources */, + 6BA3A78022B54DA400AD51ED /* CollectionViewDiffableDataSource.swift in Sources */, + 6BB762EC22B455520050DC03 /* DiffableDataSourceSnapshot.swift in Sources */, + 6B46555E22B7F904005202D1 /* UniversalError.swift in Sources */, + 6BA3A7B122B6A5CE00AD51ED /* CocoaCollectionViewDiffableDataSource.swift in Sources */, + 6BB762EB22B455520050DC03 /* SnapshotStructure.swift in Sources */, + 6BA3A79422B5A35F00AD51ED /* DiffableDataSourceCore.swift in Sources */, + 6B46555C22B6BC81005202D1 /* HashableExtension.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6BB762D122B455170050DC03 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BA4F52B22BC0AF3008D5117 /* MainThreadSerialDispatcherTests.swift in Sources */, + 6BA3A7B622B820DA00AD51ED /* TableViewDiffableDataSourceTests.swift in Sources */, + 6BB762F122B455590050DC03 /* DiffableDataSourceTests.swift in Sources */, + 6B466B2422B96A520035D72F /* CocoaCollectionViewDiffableDataSourceTests.swift in Sources */, + 6B466B2222B969600035D72F /* CollectionViewDiffableDataSourceTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 6BB762D822B455170050DC03 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6BB762CB22B455170050DC03 /* DiffableDataSources */; + targetProxy = 6BB762D722B455170050DC03 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 6BB762DE22B455170050DC03 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6BB762F422B455610050DC03 /* DiffableDataSources.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "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 = gnu11; + 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 = 12.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 6BB762DF22B455170050DC03 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6BB762F422B455610050DC03 /* DiffableDataSources.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "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 = gnu11; + 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 = 12.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 6BB762E122B455170050DC03 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6BB762F422B455610050DC03 /* DiffableDataSources.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Sources/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.ryo.DiffableDataSources; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6BB762E222B455170050DC03 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6BB762F422B455610050DC03 /* DiffableDataSources.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Sources/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.ryo.DiffableDataSources; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 6BB762E422B455170050DC03 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6BB762F422B455610050DC03 /* DiffableDataSources.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.ryo.DiffableDataSourcesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "$(inherited)"; + }; + name = Debug; + }; + 6BB762E522B455170050DC03 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6BB762F422B455610050DC03 /* DiffableDataSources.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.ryo.DiffableDataSourcesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "$(inherited)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6BB762C622B455170050DC03 /* Build configuration list for PBXProject "DiffableDataSources" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6BB762DE22B455170050DC03 /* Debug */, + 6BB762DF22B455170050DC03 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6BB762E022B455170050DC03 /* Build configuration list for PBXNativeTarget "DiffableDataSources" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6BB762E122B455170050DC03 /* Debug */, + 6BB762E222B455170050DC03 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6BB762E322B455170050DC03 /* Build configuration list for PBXNativeTarget "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6BB762E422B455170050DC03 /* Debug */, + 6BB762E522B455170050DC03 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6BB762C322B455170050DC03 /* Project object */; +} diff --git a/DiffableDataSources.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/DiffableDataSources.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..61f32e7 --- /dev/null +++ b/DiffableDataSources.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/DiffableDataSources.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/DiffableDataSources.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/DiffableDataSources.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/DiffableDataSources.xcodeproj/xcshareddata/xcschemes/DiffableDataSources.xcscheme b/DiffableDataSources.xcodeproj/xcshareddata/xcschemes/DiffableDataSources.xcscheme new file mode 100644 index 0000000..afbe573 --- /dev/null +++ b/DiffableDataSources.xcodeproj/xcshareddata/xcschemes/DiffableDataSources.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DiffableDataSources.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/DiffableDataSources.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme new file mode 100644 index 0000000..ff13cc8 --- /dev/null +++ b/DiffableDataSources.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DiffableDataSources.xcworkspace/contents.xcworkspacedata b/DiffableDataSources.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c132334 --- /dev/null +++ b/DiffableDataSources.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/DiffableDataSources.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/DiffableDataSources.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/DiffableDataSources.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Examples/DiffableDataSourcesExamples.xcodeproj/project.pbxproj b/Examples/DiffableDataSourcesExamples.xcodeproj/project.pbxproj new file mode 100644 index 0000000..bdc8ab6 --- /dev/null +++ b/Examples/DiffableDataSourcesExamples.xcodeproj/project.pbxproj @@ -0,0 +1,573 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 6B02216222BA0EF500AEB14D /* InsertionSortViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B02216122BA0EF500AEB14D /* InsertionSortViewController.swift */; }; + 6B02216422BA11D500AEB14D /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B02216322BA11D500AEB14D /* Extension.swift */; }; + 6B02216622BA177F00AEB14D /* InsertionSortViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6B02216522BA177F00AEB14D /* InsertionSortViewController.xib */; }; + 6B02216822BB7FA800AEB14D /* TopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B02216722BB7FA800AEB14D /* TopViewController.swift */; }; + 6B02216A22BB824600AEB14D /* TopViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6B02216922BB824600AEB14D /* TopViewController.xib */; }; + 6B46555622B6AB0C005202D1 /* MountainsRawData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B46555522B6AB0C005202D1 /* MountainsRawData.swift */; }; + 6B46555822B6AB15005202D1 /* MountainsRawData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B46555722B6AB15005202D1 /* MountainsRawData.swift */; }; + 6B46555A22B6AC83005202D1 /* LabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B46555922B6AC83005202D1 /* LabelItem.swift */; }; + 6BA3A78222B5515D00AD51ED /* LabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA3A78122B5515D00AD51ED /* LabelCell.swift */; }; + 6BA3A78422B5516500AD51ED /* LabelCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BA3A78322B5516500AD51ED /* LabelCell.xib */; }; + 6BA3A78822B5565E00AD51ED /* MountainsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BA3A78722B5565E00AD51ED /* MountainsViewController.xib */; }; + 6BA3A78E22B55FB000AD51ED /* DiffableDataSources.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BA3A77B22B549B700AD51ED /* DiffableDataSources.framework */; }; + 6BA3A78F22B55FB000AD51ED /* DiffableDataSources.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6BA3A77B22B549B700AD51ED /* DiffableDataSources.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6BA3A79022B55FB000AD51ED /* DifferenceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BA3A77D22B549B700AD51ED /* DifferenceKit.framework */; }; + 6BA3A79122B55FB000AD51ED /* DifferenceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6BA3A77D22B549B700AD51ED /* DifferenceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6BA3A79C22B69EDF00AD51ED /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA3A79B22B69EDF00AD51ED /* AppDelegate.swift */; }; + 6BA3A79E22B69EDF00AD51ED /* MountainsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA3A79D22B69EDF00AD51ED /* MountainsViewController.swift */; }; + 6BA3A7A022B69EE200AD51ED /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6BA3A79F22B69EE200AD51ED /* Assets.xcassets */; }; + 6BA3A7A322B69EE200AD51ED /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6BA3A7A122B69EE200AD51ED /* Main.storyboard */; }; + 6BA3A7A922B6A04F00AD51ED /* DiffableDataSources.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BA3A77B22B549B700AD51ED /* DiffableDataSources.framework */; }; + 6BA3A7AA22B6A04F00AD51ED /* DiffableDataSources.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6BA3A77B22B549B700AD51ED /* DiffableDataSources.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6BA3A7AB22B6A04F00AD51ED /* DifferenceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BA3A77D22B549B700AD51ED /* DifferenceKit.framework */; }; + 6BA3A7AC22B6A04F00AD51ED /* DifferenceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6BA3A77D22B549B700AD51ED /* DifferenceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6BB7630722B545240050DC03 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB7630622B545240050DC03 /* AppDelegate.swift */; }; + 6BB7630922B545240050DC03 /* MountainsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB7630822B545240050DC03 /* MountainsViewController.swift */; }; + 6BB7630E22B545260050DC03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6BB7630D22B545260050DC03 /* Assets.xcassets */; }; + 6BB7631122B545260050DC03 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6BB7630F22B545260050DC03 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 6BA3A79222B55FB000AD51ED /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 6BA3A79122B55FB000AD51ED /* DifferenceKit.framework in Embed Frameworks */, + 6BA3A78F22B55FB000AD51ED /* DiffableDataSources.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 6BA3A7AD22B6A05000AD51ED /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 6BA3A7AC22B6A04F00AD51ED /* DifferenceKit.framework in Embed Frameworks */, + 6BA3A7AA22B6A04F00AD51ED /* DiffableDataSources.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 6B02216122BA0EF500AEB14D /* InsertionSortViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertionSortViewController.swift; sourceTree = ""; }; + 6B02216322BA11D500AEB14D /* Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; + 6B02216522BA177F00AEB14D /* InsertionSortViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InsertionSortViewController.xib; sourceTree = ""; }; + 6B02216722BB7FA800AEB14D /* TopViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopViewController.swift; sourceTree = ""; }; + 6B02216922BB824600AEB14D /* TopViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TopViewController.xib; sourceTree = ""; }; + 6B46555522B6AB0C005202D1 /* MountainsRawData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MountainsRawData.swift; sourceTree = SOURCE_ROOT; }; + 6B46555722B6AB15005202D1 /* MountainsRawData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MountainsRawData.swift; sourceTree = SOURCE_ROOT; }; + 6B46555922B6AC83005202D1 /* LabelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelItem.swift; sourceTree = ""; }; + 6BA3A77B22B549B700AD51ED /* DiffableDataSources.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DiffableDataSources.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6BA3A77D22B549B700AD51ED /* DifferenceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DifferenceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6BA3A78122B5515D00AD51ED /* LabelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCell.swift; sourceTree = ""; }; + 6BA3A78322B5516500AD51ED /* LabelCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LabelCell.xib; sourceTree = ""; }; + 6BA3A78722B5565E00AD51ED /* MountainsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MountainsViewController.xib; sourceTree = ""; }; + 6BA3A79922B69EDF00AD51ED /* Example-macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6BA3A79B22B69EDF00AD51ED /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 6BA3A79D22B69EDF00AD51ED /* MountainsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MountainsViewController.swift; sourceTree = ""; }; + 6BA3A79F22B69EE200AD51ED /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6BA3A7A222B69EE200AD51ED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 6BA3A7A422B69EE200AD51ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6BA3A7A522B69EE200AD51ED /* Example_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example_macOS.entitlements; sourceTree = ""; }; + 6BB7630322B545240050DC03 /* Example-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6BB7630622B545240050DC03 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 6BB7630822B545240050DC03 /* MountainsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MountainsViewController.swift; sourceTree = ""; }; + 6BB7630D22B545260050DC03 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6BB7631022B545260050DC03 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 6BB7631222B545260050DC03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6BA3A79622B69EDF00AD51ED /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BA3A7AB22B6A04F00AD51ED /* DifferenceKit.framework in Frameworks */, + 6BA3A7A922B6A04F00AD51ED /* DiffableDataSources.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6BB7630022B545240050DC03 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BA3A79022B55FB000AD51ED /* DifferenceKit.framework in Frameworks */, + 6BA3A78E22B55FB000AD51ED /* DiffableDataSources.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6BA3A79A22B69EDF00AD51ED /* Example-macOS */ = { + isa = PBXGroup; + children = ( + 6BA3A79D22B69EDF00AD51ED /* MountainsViewController.swift */, + 6B46555922B6AC83005202D1 /* LabelItem.swift */, + 6B46555722B6AB15005202D1 /* MountainsRawData.swift */, + 6BA3A79B22B69EDF00AD51ED /* AppDelegate.swift */, + 6BA3A7A122B69EE200AD51ED /* Main.storyboard */, + 6BA3A79F22B69EE200AD51ED /* Assets.xcassets */, + 6BA3A7A422B69EE200AD51ED /* Info.plist */, + 6BA3A7A522B69EE200AD51ED /* Example_macOS.entitlements */, + ); + path = "Example-macOS"; + sourceTree = ""; + }; + 6BB762FA22B545240050DC03 = { + isa = PBXGroup; + children = ( + 6BB7630522B545240050DC03 /* Example-iOS */, + 6BA3A79A22B69EDF00AD51ED /* Example-macOS */, + 6BB7630422B545240050DC03 /* Products */, + 6BB7632122B5467B0050DC03 /* Frameworks */, + ); + sourceTree = ""; + }; + 6BB7630422B545240050DC03 /* Products */ = { + isa = PBXGroup; + children = ( + 6BB7630322B545240050DC03 /* Example-iOS.app */, + 6BA3A79922B69EDF00AD51ED /* Example-macOS.app */, + ); + name = Products; + sourceTree = ""; + }; + 6BB7630522B545240050DC03 /* Example-iOS */ = { + isa = PBXGroup; + children = ( + 6B02216722BB7FA800AEB14D /* TopViewController.swift */, + 6B02216922BB824600AEB14D /* TopViewController.xib */, + 6BB7630822B545240050DC03 /* MountainsViewController.swift */, + 6BA3A78722B5565E00AD51ED /* MountainsViewController.xib */, + 6B02216122BA0EF500AEB14D /* InsertionSortViewController.swift */, + 6B02216522BA177F00AEB14D /* InsertionSortViewController.xib */, + 6BA3A78122B5515D00AD51ED /* LabelCell.swift */, + 6BA3A78322B5516500AD51ED /* LabelCell.xib */, + 6B02216322BA11D500AEB14D /* Extension.swift */, + 6B46555522B6AB0C005202D1 /* MountainsRawData.swift */, + 6BB7630622B545240050DC03 /* AppDelegate.swift */, + 6BB7630D22B545260050DC03 /* Assets.xcassets */, + 6BB7630F22B545260050DC03 /* LaunchScreen.storyboard */, + 6BB7631222B545260050DC03 /* Info.plist */, + ); + path = "Example-iOS"; + sourceTree = ""; + }; + 6BB7632122B5467B0050DC03 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6BA3A77B22B549B700AD51ED /* DiffableDataSources.framework */, + 6BA3A77D22B549B700AD51ED /* DifferenceKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6BA3A79822B69EDF00AD51ED /* Example-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6BA3A7A822B69EE200AD51ED /* Build configuration list for PBXNativeTarget "Example-macOS" */; + buildPhases = ( + 6BA3A79522B69EDF00AD51ED /* Sources */, + 6BA3A79622B69EDF00AD51ED /* Frameworks */, + 6BA3A79722B69EDF00AD51ED /* Resources */, + 6BA3A7AD22B6A05000AD51ED /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Example-macOS"; + productName = "Example-macOS"; + productReference = 6BA3A79922B69EDF00AD51ED /* Example-macOS.app */; + productType = "com.apple.product-type.application"; + }; + 6BB7630222B545240050DC03 /* Example-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6BB7631522B545260050DC03 /* Build configuration list for PBXNativeTarget "Example-iOS" */; + buildPhases = ( + 6BB762FF22B545240050DC03 /* Sources */, + 6BB7630022B545240050DC03 /* Frameworks */, + 6BB7630122B545240050DC03 /* Resources */, + 6BA3A79222B55FB000AD51ED /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Example-iOS"; + productName = "DiffableDataSourcesExample-iOS"; + productReference = 6BB7630322B545240050DC03 /* Example-iOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6BB762FB22B545240050DC03 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1020; + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "Ryo Aoyama"; + TargetAttributes = { + 6BA3A79822B69EDF00AD51ED = { + CreatedOnToolsVersion = 10.2.1; + }; + 6BB7630222B545240050DC03 = { + CreatedOnToolsVersion = 10.2.1; + }; + }; + }; + buildConfigurationList = 6BB762FE22B545240050DC03 /* Build configuration list for PBXProject "DiffableDataSourcesExamples" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6BB762FA22B545240050DC03; + productRefGroup = 6BB7630422B545240050DC03 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6BB7630222B545240050DC03 /* Example-iOS */, + 6BA3A79822B69EDF00AD51ED /* Example-macOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6BA3A79722B69EDF00AD51ED /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BA3A7A022B69EE200AD51ED /* Assets.xcassets in Resources */, + 6BA3A7A322B69EE200AD51ED /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6BB7630122B545240050DC03 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BA3A78422B5516500AD51ED /* LabelCell.xib in Resources */, + 6B02216622BA177F00AEB14D /* InsertionSortViewController.xib in Resources */, + 6B02216A22BB824600AEB14D /* TopViewController.xib in Resources */, + 6BA3A78822B5565E00AD51ED /* MountainsViewController.xib in Resources */, + 6BB7631122B545260050DC03 /* LaunchScreen.storyboard in Resources */, + 6BB7630E22B545260050DC03 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6BA3A79522B69EDF00AD51ED /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BA3A79E22B69EDF00AD51ED /* MountainsViewController.swift in Sources */, + 6B46555A22B6AC83005202D1 /* LabelItem.swift in Sources */, + 6B46555822B6AB15005202D1 /* MountainsRawData.swift in Sources */, + 6BA3A79C22B69EDF00AD51ED /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6BB762FF22B545240050DC03 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BB7630922B545240050DC03 /* MountainsViewController.swift in Sources */, + 6BA3A78222B5515D00AD51ED /* LabelCell.swift in Sources */, + 6B02216422BA11D500AEB14D /* Extension.swift in Sources */, + 6B02216222BA0EF500AEB14D /* InsertionSortViewController.swift in Sources */, + 6BB7630722B545240050DC03 /* AppDelegate.swift in Sources */, + 6B46555622B6AB0C005202D1 /* MountainsRawData.swift in Sources */, + 6B02216822BB7FA800AEB14D /* TopViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 6BA3A7A122B69EE200AD51ED /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6BA3A7A222B69EE200AD51ED /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 6BB7630F22B545260050DC03 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6BB7631022B545260050DC03 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 6BA3A7A622B69EE200AD51ED /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Example-macOS/Example_macOS.entitlements"; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "Example-macOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_BUNDLE_IDENTIFIER = "com.ryo.DiffableDataSourcesExample-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 6BA3A7A722B69EE200AD51ED /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Example-macOS/Example_macOS.entitlements"; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "Example-macOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_BUNDLE_IDENTIFIER = "com.ryo.DiffableDataSourcesExample-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 6BB7631322B545260050DC03 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = 12.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 6BB7631422B545260050DC03 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "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 = gnu11; + 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 = 12.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6BB7631622B545260050DC03 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_OPTIMIZATION_LEVEL = s; + INFOPLIST_FILE = "Example-iOS/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.ryo.DiffableDataSourcesExample-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = ""; + }; + name = Debug; + }; + 6BB7631722B545260050DC03 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "Example-iOS/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.ryo.DiffableDataSourcesExample-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6BA3A7A822B69EE200AD51ED /* Build configuration list for PBXNativeTarget "Example-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6BA3A7A622B69EE200AD51ED /* Debug */, + 6BA3A7A722B69EE200AD51ED /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6BB762FE22B545240050DC03 /* Build configuration list for PBXProject "DiffableDataSourcesExamples" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6BB7631322B545260050DC03 /* Debug */, + 6BB7631422B545260050DC03 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6BB7631522B545260050DC03 /* Build configuration list for PBXNativeTarget "Example-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6BB7631622B545260050DC03 /* Debug */, + 6BB7631722B545260050DC03 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6BB762FB22B545240050DC03 /* Project object */; +} diff --git a/Examples/DiffableDataSourcesExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/DiffableDataSourcesExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..7d8a8d7 --- /dev/null +++ b/Examples/DiffableDataSourcesExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/DiffableDataSourcesExamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/DiffableDataSourcesExamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Examples/DiffableDataSourcesExamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Examples/DiffableDataSourcesExamples.xcodeproj/xcshareddata/xcschemes/Example-iOS.xcscheme b/Examples/DiffableDataSourcesExamples.xcodeproj/xcshareddata/xcschemes/Example-iOS.xcscheme new file mode 100644 index 0000000..847b6aa --- /dev/null +++ b/Examples/DiffableDataSourcesExamples.xcodeproj/xcshareddata/xcschemes/Example-iOS.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/DiffableDataSourcesExamples.xcworkspace/contents.xcworkspacedata b/Examples/DiffableDataSourcesExamples.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..d994853 --- /dev/null +++ b/Examples/DiffableDataSourcesExamples.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/Examples/DiffableDataSourcesExamples.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/DiffableDataSourcesExamples.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Examples/DiffableDataSourcesExamples.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Examples/Example-iOS/AppDelegate.swift b/Examples/Example-iOS/AppDelegate.swift new file mode 100644 index 0000000..b4dcb37 --- /dev/null +++ b/Examples/Example-iOS/AppDelegate.swift @@ -0,0 +1,17 @@ +import UIKit + +@UIApplicationMain +final class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + let window = UIWindow() + let navigationController = UINavigationController(rootViewController: TopViewController()) + navigationController.navigationBar.tintColor = .black + window.rootViewController = navigationController + window.makeKeyAndVisible() + self.window = window + + return true + } +} diff --git a/Examples/Example-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Example-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ b/Examples/Example-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "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" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/Example-iOS/Assets.xcassets/Contents.json b/Examples/Example-iOS/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Examples/Example-iOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/Example-iOS/Base.lproj/LaunchScreen.storyboard b/Examples/Example-iOS/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..bfa3612 --- /dev/null +++ b/Examples/Example-iOS/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS/Extension.swift b/Examples/Example-iOS/Extension.swift new file mode 100644 index 0000000..013cb33 --- /dev/null +++ b/Examples/Example-iOS/Extension.swift @@ -0,0 +1,13 @@ +import UIKit + +extension UITableViewCell { + static var name: String { + return String(describing: self) + } +} + +extension UICollectionViewCell { + static var name: String { + return String(describing: self) + } +} diff --git a/Examples/Example-iOS/Info.plist b/Examples/Example-iOS/Info.plist new file mode 100644 index 0000000..fa1dc15 --- /dev/null +++ b/Examples/Example-iOS/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/Example-iOS/InsertionSortViewController.swift b/Examples/Example-iOS/InsertionSortViewController.swift new file mode 100644 index 0000000..9b5855f --- /dev/null +++ b/Examples/Example-iOS/InsertionSortViewController.swift @@ -0,0 +1,172 @@ +import UIKit +import DiffableDataSources + +final class InsertionSortViewController: UIViewController { + final class Section: Hashable { + var id = UUID() + var nodes: [Node] + private(set) var isSorted = false + private var currentIndex = 1 + + init(count: Int) { + nodes = (0.. 1 else { + return isSorted = true + } + + var index = currentIndex + let currentNode = nodes[index] + index -= 1 + + while index >= 0 && currentNode.value < nodes[index].value { + let node = nodes[index] + nodes[index] = currentNode + nodes[index + 1] = node + index -= 1 + } + + currentIndex += 1 + + if currentIndex >= nodes.count { + isSorted = true + } + } + + static func == (lhs: Section, rhs: Section) -> Bool { + return lhs.id == rhs.id + } + } + + struct Node: Hashable { + var id = UUID() + var value: Int + var color: UIColor + + init(value: Int, maxValue: Int) { + let hue = CGFloat(value) / CGFloat(maxValue) + self.value = value + color = UIColor(hue: hue, saturation: 1, brightness: 1, alpha: 1) + id = UUID() + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + + static func == (lhs: Node, rhs: Node) -> Bool { + return lhs.id == rhs.id + } + } + + @IBOutlet private var collectionView: UICollectionView! + private var isSorting = false + + let nodeSize = CGSize(width: 16, height: 34) + + private lazy var dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, node in + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: UICollectionViewCell.name, for: indexPath) + cell.backgroundColor = node.color + return cell + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Insertion Sort" + + collectionView.delegate = self + collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: UICollectionViewCell.name) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: self, action: #selector(toggleSort)) + + updateSortButtonTitle() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + randmize(animated: false) + } + + @objc func toggleSort() { + isSorting.toggle() + updateSortButtonTitle() + + if isSorting { + startInsertionSort() + } + } + + func updateSortButtonTitle() { + navigationItem.rightBarButtonItem?.title = isSorting ? "Stop" : "Sort" + } + + func randmize(animated: Bool) { + let snapshot = DiffableDataSourceSnapshot() + let rows = Int(collectionView.bounds.height / nodeSize.height) - 1 + let columns = Int(collectionView.bounds.width / nodeSize.width) + + for _ in 0.. CGFloat { + return 0 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 0 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return .zero + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return nodeSize + } +} diff --git a/Examples/Example-iOS/InsertionSortViewController.xib b/Examples/Example-iOS/InsertionSortViewController.xib new file mode 100644 index 0000000..6ad211b --- /dev/null +++ b/Examples/Example-iOS/InsertionSortViewController.xib @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS/LabelCell.swift b/Examples/Example-iOS/LabelCell.swift new file mode 100644 index 0000000..1be3da4 --- /dev/null +++ b/Examples/Example-iOS/LabelCell.swift @@ -0,0 +1,12 @@ +import UIKit + +final class LabelCell: UICollectionViewCell { + @IBOutlet var label: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + + layer.borderWidth = 1 + layer.borderColor = UIColor.gray.cgColor + } +} diff --git a/Examples/Example-iOS/LabelCell.xib b/Examples/Example-iOS/LabelCell.xib new file mode 100644 index 0000000..78671b6 --- /dev/null +++ b/Examples/Example-iOS/LabelCell.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS/MountainsViewController.swift b/Examples/Example-iOS/MountainsViewController.swift new file mode 100644 index 0000000..a0e8150 --- /dev/null +++ b/Examples/Example-iOS/MountainsViewController.swift @@ -0,0 +1,84 @@ +import UIKit +import DiffableDataSources + +final class MountainsViewController: UIViewController { + enum Section { + case main + } + + struct Mountain: Hashable { + var name: String + + func contains(_ filter: String) -> Bool { + guard !filter.isEmpty else { + return true + } + + let lowercasedFilter = filter.lowercased() + return name.lowercased().contains(lowercasedFilter) + } + } + + @IBOutlet private var searchBar: UISearchBar! + @IBOutlet private var collectionView: UICollectionView! + + private lazy var dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, mountain in + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LabelCell.name, for: indexPath) as! LabelCell + cell.label.text = mountain.name + return cell + } + + private let allMountains: [Mountain] = mountainsRawData.components(separatedBy: .newlines).map { line in + let name = line.components(separatedBy: ",")[0] + return Mountain(name: name) + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Mountains Search" + + searchBar.delegate = self + collectionView.delegate = self + collectionView.register(UINib(nibName: LabelCell.name, bundle: .main), forCellWithReuseIdentifier: LabelCell.name) + + search(filter: "") + } + + func search(filter: String) { + let mountains = allMountains.lazy + .filter { $0.contains(filter) } + .sorted { $0.name < $1.name } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(mountains) + dataSource.apply(snapshot) + } +} + +extension MountainsViewController: UISearchBarDelegate { + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + search(filter: searchText) + } +} + +extension MountainsViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 10 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 10 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let column = 2 + let width = (collectionView.bounds.width - 10 * CGFloat(column + 1)) / CGFloat(column) + return CGSize(width: width, height: 32) + } +} diff --git a/Examples/Example-iOS/MountainsViewController.xib b/Examples/Example-iOS/MountainsViewController.xib new file mode 100644 index 0000000..c208711 --- /dev/null +++ b/Examples/Example-iOS/MountainsViewController.xib @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS/TopViewController.swift b/Examples/Example-iOS/TopViewController.swift new file mode 100644 index 0000000..c0c067f --- /dev/null +++ b/Examples/Example-iOS/TopViewController.swift @@ -0,0 +1,82 @@ +import UIKit +import DiffableDataSources + +final class TopViewController: UIViewController { + enum Section { + case main + } + + enum Item { + case mountains + case insertionSort + } + + @IBOutlet private var tableView: UITableView! + + private lazy var dataSource = TableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in + let cell = tableView.dequeueReusableCell(withIdentifier: UITableViewCell.name, for: indexPath) + cell.textLabel?.text = item.title + cell.accessoryType = .disclosureIndicator + return cell + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "DiffableDataSources" + + tableView.delegate = self + tableView.register(UITableViewCell.self, forCellReuseIdentifier: UITableViewCell.name) + tableView.rowHeight = 60 + tableView.contentInset.top = 30 + + reset() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let indexPath = tableView.indexPathForSelectedRow { + tableView.deselectRow(at: indexPath, animated: true) + } + } + + func reset() { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems([.mountains, .insertionSort]) + dataSource.apply(snapshot) + } +} + +extension TopViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let item = dataSource.itemIdentifier(for: indexPath) else { + return + } + + let viewController: UIViewController + + switch item { + case .mountains: + viewController = MountainsViewController() + + case .insertionSort: + viewController = InsertionSortViewController() + } + + navigationController?.pushViewController(viewController, animated: true) + } +} + +private extension TopViewController.Item { + var title: String { + switch self { + case .mountains: + return "🗻 Mountains" + + case .insertionSort: + return "📱 Insertion Sort" + } + } +} diff --git a/Examples/Example-iOS/TopViewController.xib b/Examples/Example-iOS/TopViewController.xib new file mode 100644 index 0000000..aba2f69 --- /dev/null +++ b/Examples/Example-iOS/TopViewController.xib @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-macOS/AppDelegate.swift b/Examples/Example-macOS/AppDelegate.swift new file mode 100644 index 0000000..79096a6 --- /dev/null +++ b/Examples/Example-macOS/AppDelegate.swift @@ -0,0 +1,4 @@ +import Cocoa + +@NSApplicationMain +final class AppDelegate: NSObject, NSApplicationDelegate {} diff --git a/Examples/Example-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Example-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2db2b1c --- /dev/null +++ b/Examples/Example-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/Example-macOS/Assets.xcassets/Contents.json b/Examples/Example-macOS/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Examples/Example-macOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/Example-macOS/Base.lproj/Main.storyboard b/Examples/Example-macOS/Base.lproj/Main.storyboard new file mode 100644 index 0000000..2556453 --- /dev/null +++ b/Examples/Example-macOS/Base.lproj/Main.storyboard @@ -0,0 +1,773 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-macOS/Example_macOS.entitlements b/Examples/Example-macOS/Example_macOS.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/Examples/Example-macOS/Example_macOS.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/Examples/Example-macOS/Info.plist b/Examples/Example-macOS/Info.plist new file mode 100644 index 0000000..e5db660 --- /dev/null +++ b/Examples/Example-macOS/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2019 Ryo Aoyama. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/Examples/Example-macOS/LabelItem.swift b/Examples/Example-macOS/LabelItem.swift new file mode 100644 index 0000000..47e72d1 --- /dev/null +++ b/Examples/Example-macOS/LabelItem.swift @@ -0,0 +1,31 @@ +import AppKit + +final class LabelItem: NSCollectionViewItem { + static var itemIdentifier: NSUserInterfaceItemIdentifier { + return NSUserInterfaceItemIdentifier(String(describing: self)) + } + + let label = NSTextField() + + override func loadView() { + view = NSView() + } + + override func viewDidLoad() { + super.viewDidLoad() + + label.textColor = .gray + label.font = .systemFont(ofSize: 16) + label.isEditable = false + label.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(label) + + let constraints = [ + label.topAnchor.constraint(equalTo: view.topAnchor), + label.bottomAnchor.constraint(equalTo: view.bottomAnchor), + label.leadingAnchor.constraint(equalTo: view.leadingAnchor), + label.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ] + NSLayoutConstraint.activate(constraints) + } +} diff --git a/Examples/Example-macOS/MountainsViewController.swift b/Examples/Example-macOS/MountainsViewController.swift new file mode 100644 index 0000000..17391d2 --- /dev/null +++ b/Examples/Example-macOS/MountainsViewController.swift @@ -0,0 +1,81 @@ +import AppKit +import DiffableDataSources + +final class MountainsViewController: NSViewController { + enum Section { + case main + } + + struct Mountain: Hashable { + var name: String + + func contains(_ filter: String) -> Bool { + guard !filter.isEmpty else { + return true + } + + let lowercasedFilter = filter.lowercased() + return name.lowercased().contains(lowercasedFilter) + } + } + + @IBOutlet private var searchField: NSSearchField! + @IBOutlet private var collectionView: NSCollectionView! + + private lazy var dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, mountain in + let item = collectionView.makeItem(withIdentifier: LabelItem.itemIdentifier, for: indexPath) as! LabelItem + item.label.stringValue = mountain.name + return item + } + + private let allMountains: [Mountain] = mountainsRawData.components(separatedBy: .newlines).map { line in + let name = line.components(separatedBy: ",")[0] + return Mountain(name: name) + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Mountains Search" + + collectionView.delegate = self + collectionView.register(LabelItem.self, forItemWithIdentifier: LabelItem.itemIdentifier) + + search(filter: "") + } + + func search(filter: String) { + let mountains = allMountains.lazy + .filter { $0.contains(filter) } + .sorted { $0.name < $1.name } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(mountains) + dataSource.apply(snapshot) + } + + @IBAction func searchFieldDidChangeText(_ sender: NSSearchField) { + search(filter: sender.stringValue) + } +} + +extension MountainsViewController: NSCollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 10 + } + + func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 10 + } + + func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> NSEdgeInsets { + return NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + } + + func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { + let column = 3 + let width = (collectionView.bounds.width - 10 * CGFloat(column + 1)) / CGFloat(column) + return CGSize(width: width, height: 44) + } +} diff --git a/Examples/MountainsRawData.swift b/Examples/MountainsRawData.swift new file mode 100644 index 0000000..225ee77 --- /dev/null +++ b/Examples/MountainsRawData.swift @@ -0,0 +1,391 @@ +// https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/using_collection_view_compositional_layouts_and_diffable_data_sources + +let mountainsRawData = """ +Mount Everest,8848 +K2,8611 +Kangchenjunga,8586 +Lhotse,8516 +Makalu,8485 +Cho Oyu,8201 +Dhaulagiri,8167 +Manaslu,8163 +Nanga Parbat,8126 +Annapurna,8091 +Gasherbrum I,8080 +Broad Peak,8051 +Gasherbrum II,8035 +Shishapangma,8027 +Gasherbrum III,7952 +Gyachung Kang,7952 +Annapurna II,7937 +Gasherbrum IV,7932 +Himalchuli,7893 +Distaghil Sar,7885 +Ngadi Chuli,7871 +Nuptse,7861 +Khunyang Chhish,7852 +Masherbrum,7821 +Nanda Devi,7816 +Chomo Lonzo,7804 +Batura Sar,7795 +Kanjut Sar,7790 +Rakaposhi,7788 +Namcha Barwa,7782 +Kamet,7756 +Saltoro Kangri,7742 +Jannu,7710 +Tirich Mir,7708 +Molamenqing,7703 +Gurla Mandhata,7694 +Saser Kangri,7672 +Chogolisa,7665 +Kongur Tagh,7649 +Shispare,7611 +Silberzacken,7597 +Changtse,7583 +Trivor,7577 +Gangkhar Puensum,7570 +Gongga Shan,7556 +Annapurna III,7555 +Kula Kangri,7554 +Muztagh Ata,7546 +Skyang Kangri,7545 +Liankang Kangri,7535 +Yukshin Gardan Sar,7530 +Annapurna IV,7525 +Mamostong Kangri,7516 +Ismoil Somoni Peak,7495 +Noshaq,7492 +Pumari Chhish,7492 +Passu Sar,7476 +Malubiting,7458 +Gangapurna,7455 +Muchu Chhish (Batura V),7453 +Jengish Chokusu,7439 +K12,7428 +Sia Kangri,7422 +Istor-o-Nal,7403 +Ghent Kangri,7401 +Haramosh Peak,7397 +Kabru,7394 +Ultar,7388 +Rimo I,7385 +Sherpi Kangri,7380 +Labuche Kang,7367 +Kirat Chuli,7365 +Skil Brum,7360 +Gimmigela Chuli,7350 +Bojohagur Duanasir,7329 +Chamlang,7319 +Jomolhari / Chomolhari,7314 +Baltoro Kangri,7312 +Siguang Ri,7308 +The Crown,7295 +Gyala Peri,7294 +Porong Ri,7292 +Baintha Brakk,7285 +Yutmaru Sar,7283 +K6,7282 +Kangpenqing,7281 +Mana Peak,7272 +Muztagh Tower,7273 +Diran,7257 +Apsarasas Kangri,7245 +Langtang Lirung,7227 +Khartaphu,7213 +Tongshanjiabu,7207 +Langtang Ri,7205 +Kangphu Kang,7204 +Annapurna South,7219 +Melungtse,7181 +Liushi Shan,7167 +Baruntse,7162 +Pumori,7161 +Hardeol,7151 +Gasherbrum V,7147 +Latok I,7145 +Nemjung,7140 +Udren Zom,7140 +Chaukhamba,7138 +Nun Kun,7135 +Tilicho Peak,7134 +Gauri Sankar,7134 +Lenin Peak,7134 +Api,7132 +Pauhunri,7128 +Trisul,7120 +Korzhenevskaya,7105 +Lunpo Gangri,7095 +Satopanth,7075 +Tirsuli,7074 +Dunagiri,7066 +Kangto,7060 +Nyegyi Kansang,7047 +Link Sar,7041 +Kezhen Peak,7038 +Shah Dhar,7038 +Spantik,7027 +Khan Tengri,7010 +Machapuchare,6993 +Laila Peak (Haramosh Valley),6985 +Kang Guru,6981 +Gasherbrum VI,6979 +Karun Kuh,6977 +Avicenna Peak,6974 +Ulugh Muztagh,6973 +Aconcagua,6962 +Napko Kangri,6956 +Kedarnath,6940 +K7,6934 +Panchchuli,6904 +Thalay Sagar,6904 +Lunkho e Dosare,6901 +Lunag Ri,6895 +Ojos del Salado,6891 +Siniolchu,6888 +Kanjiroba,6883 +Bairiga,6882 +Koyo Zom,6872 +Nanda Kot,6861 +Kubi Gangri,6859 +Angel Sar,6858 +Bhagirathi I,6856 +Jethi Bahurani,6850 +Chongra Peak,6830 +Reo Purgyil,6816 +Ama Dablam,6812 +Monte Pissis,6795 +Biarchedi,6781 +Huascarán Sur,6768 +Cerro Bonete,6759 +Nevado Tres Cruces,6748 +Kawagarbo,6740 +Llullaillaco,6739 +Cho Polu,6735 +Kangju Kangri,6725 +Changla,6721 +Mercedario,6720 +Mount Pandim,6691 +Num Ri,6677 +Lungser Kangri,6666 +Meru Peak,6660 +Gul Lasht Zom,6657 +Huascarán Norte,6655 +Khumbutse,6640 +Mount Kailash,6638 +Yerupajá,6635 +Nevado Tres Cruces Central,6629 +Thamserku,6623 +Geladaindong Peak,6621 +Incahuasi,6621 +Pangpoche,6620 +Manirang,6597 +Nilkantha,6596 +Phuparash Peak,6574 +Sickle Moon Peak,6574 +Buni Zom,6542 +Nevado Sajama,6542 +Ghamubar Zom,6518 +Singu Chuli,6501 +Taboche,6501 +Cerro El Muerto,6488 +Mera Peak,6476 +Hiunchuli,6441 +Cholatse,6440 +Illimani,6438 +Ancohuma,6427 +Coropuna,6425 +Antofalla,6409 +Kang Yatze,6400 +Huandoy,6395 +Ausangate,6384 +Illampu,6368 +Kusum Kangguru,6367 +Kinnaur Kailash,6349 +Parinaquta,6348 +Siula Grande,6344 +Bamba Dhura,6334 +Ampato,6288 +Amne Machin,6282 +Pomerape,6282 +Salcantay,6271 +Chimborazo,6267 +Mount Siguniang,6250 +Grid Nie Mountain,6224 +Yuzhu Peak,6224 +Genyen Massif,6204 +Kongde Ri,6187 +Aucanquilcha,6176 +Imja Tse,6189 +Denali (Mt. McKinley),6168 +Stok Kangri,6137 +Lobuche,6119 +Marmolejo,6108 +Laila Peak (Hushe Valley),6096 +Pisang Peak,6091 +Huayna Potosí,6088 +Aracar,6082 +Chachakumani,6074 +Chachani,6057 +Mianzimu,6054 +Acotango,6052 +Socompa,6051 +Acamarachi,6046 +Shayaz,6026 +Hualca Hualca,6025 +Uturunku,6020 +Mitre Peak,6010 +Laila Peak,5971 +Mount Logan,5959 +Alpamayo,5947 +Cerro Lípez,5929 +Licancabur,5920 +Falak Sar,5918 +Cotopaxi,5897 +Mount Kilimanjaro,5895 +Hkakabo Razi,5881 +San José,5856 +El Misti,5822 +Altun Shan,5798 +Cayambe,5790 +Pico Cristóbal Colón,5776 +Antisana,5753 +Nevado Pisco,5752 +Nevado Anallajsi,5750 +Pokalde,5745 +Ubinas,5672 +Pichu Pichu,5664 +Mount Elbrus,5642 +Mehrbani Peak,5639 +Pico de Orizaba,5636 +Mount Damavand,5610 +Nevado Mismi,5597 +Jade Dragon Snow Mountain,5596 +Lascar Volcano,5592 +Mount Xuebaoding,5588 +Kala Patthar,5545 +Mount Saint Elias,5489 +Concord Peak,5469 +Machoi Peak,5458 +El Plomo,5450 +Bogda Feng,5445 +Mount Little Xuebaoding,5443 +Cerro El Plomo,5434 +Popocatépetl,5426 +Kolahoi Peak,5425 +Chacaltaya,5421 +Mount Pomiu,5413 +Ritacuba Blanco,5410 +Haba Xueshan,5396 +Nevado del Ruiz,5389 +Nevado del Huila,5364 +El Altar,5320 +Mount Foraker,5304 +Mount Haramukh,5300 +Nevado del Tolima,5276 +Maipo,5264 +Illiniza,5248 +Sirbal Peak,5236 +Sangay,5230 +Iztaccíhuatl,5230 +Mount Lucania,5226 +Dykh-Tau,5205 +Shkhara,5201 +Mount Kenya,5199 +Malika Parbat,5190 +Amarnath Peak,5186 +King Peak,5173 +Boris Yeltsin Peak,5168 +Koshtan-Tau,5150 +Mount Ararat,5137 +Mount Stanley,5109 +Mount Steele,5073 +Janga,5051 +Mount Kazbek,5047 +Tungurahua,5023 +Carihuairazo,5018 +Mount Bona,5005 +Mount Blackburn,4996 +Pico Bolívar,4981 +Pik Talgar,4979 +Shota Rustaveli Peak,4960 +Gunshar,4950 +Mount Sanford,4949 +Pico Humboldt,4940 +Vinson Massif,4892 +Pico Bonpland,4890 +Puncak Jaya,4884 +Pico La Concha,4870 +Gistola,4860 +Tetnuldi,4858 +Mount Tyree,4852 +Huaynaputina,4850 +Alam Kuh,4850 +Mount Wood,4842 +Mount Vancouver,4812 +Sabalan,4811 +Mont Blanc,4810 +Corazón,4790 +Pichincha,4784 +Jimara,4780 +Mount Churchill,4766 +Puncak Mandala,4760 +Klyuchevskaya Sopka,4750 +Puncak Trikora,4750 +Mont Blanc de Courmayeur,4748 +Sunset Peak,4745 +Mount Slaggard,4742 +Pico Piedras Blancas,4740 +Pico El Toro,4730 +Tatakooti Peak,4725 +Rumiñahui,4721 +Pico El Leon,4720 +Ushba,4710 +Volcán Domuyo,4709 +Pico Los Nevados,4700 +Pico Pan de Azucar,4680 +Naltar Peak,4678 +Mount Fairweather,4663 +Pico Mucuñuque,4660 +Pico El Buitre,4650 +Khazret Sultan,4643 +Sierra Negra,4640 +Dufourspitze (Monte Rosa),4634 +Dunantspitze (Monte Rosa),4632 +Nordend (Monte Rosa),4609 +Mount Hubbard,4577 +Nevado de Toluca,4577 +Mount Meru,4566 +Zumsteinspitze (Monte Rosa),4563 +Signalkuppe (Monte Rosa),4554 +Dom,4545 +Ras Dashen,4533 +Eastern Liskamm (Lyskamm),4527 +Mount Bear,4521 +Mount Wilhelm,4509 +Mount Karisimbi,4507 +Mount Walsh,4507 +Belukha Mountain,4506 +Weisshorn,4506 +Tebulosmta,4493 +Täschhorn,4491 +Bazarduzu Dagi,4485 +Matterhorn,4478 +Mount Rutford,4477 +Mont Maudit,4465 +Babis Mta,4454 +Mount Shani,4451 +Dena,4448 +Vladimir Putin Peak,4446 +Mount Hunter,4442 +Parrotspitze (Monte Rosa),4432 +Mount Whitney,4421 +Mount Alverstone,4420 +University Peak,4411 +Aello Peak,4403 +Mount Elbert,4402 +Mount Massive,4395 +Mount Harvard,4395 +Mount Rainier,4392 +Kholeno,4387 +""" diff --git a/Examples/README.md b/Examples/README.md new file mode 100644 index 0000000..add24a6 --- /dev/null +++ b/Examples/README.md @@ -0,0 +1,10 @@ +

+DiffableDataSources Examples +

+ +## How to Run + +1. Clone the DiffableDataSources repository. +1. Checkout the dependencies using terminal command `$ make setup` at the project root directory. +1. Open example project workspace. +1. Run. \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..b996299 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem 'cocoapods', '1.7.2' +gem 'jazzy', '0.9.4' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..05bee78 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,103 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.0) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + atomos (0.1.3) + claide (1.0.2) + cocoapods (1.7.2) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.7.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.6.6) + nap (~> 1.0) + ruby-macho (~> 1.4) + xcodeproj (>= 1.10.0, < 2.0) + cocoapods-core (1.7.2) + activesupport (>= 4.0.2, < 6) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.2.2) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.3.1) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.1.0) + colored2 (3.1.2) + concurrent-ruby (1.1.5) + escape (0.0.4) + ffi (1.11.1) + fourflusher (2.3.0) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jazzy (0.9.4) + cocoapods (~> 1.0) + mustache (~> 0.99) + open4 + redcarpet (~> 3.2) + rouge (>= 2.0.6, < 4.0) + sass (~> 3.4) + sqlite3 (~> 1.3) + xcinvoke (~> 0.3.0) + liferaft (0.0.6) + minitest (5.11.3) + molinillo (0.6.6) + mustache (0.99.8) + nanaimo (0.2.6) + nap (1.1.0) + netrc (0.11.0) + open4 (1.3.4) + rb-fsevent (0.10.3) + rb-inotify (0.10.0) + ffi (~> 1.0) + redcarpet (3.4.0) + rouge (3.4.1) + ruby-macho (1.4.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sqlite3 (1.4.1) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + xcinvoke (0.3.0) + liferaft (~> 0.0.6) + xcodeproj (1.10.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods (= 1.7.2) + jazzy (= 0.9.4) + +BUNDLED WITH + 2.0.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file 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. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..71615ba --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +setup: + git submodule update --init --recursive + +carthage-submodule: + carthage update --no-build --use-submodules + +gems-install: + bundle install --path vendor/bundle + +docs-gen: + bundle exec jazzy --config .jazzy.yml + +lib-lint: + bundle exec pod lib lint + +pod-release: + bundle exec pod trunk push --allow-warnings diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..e5a9bc0 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "DifferenceKit", + "repositoryURL": "https://github.com/ra1028/DifferenceKit.git", + "state": { + "branch": null, + "revision": "4eb31f8e85e4cb13732f9664d6e01e507cd592a0", + "version": "1.1.3" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..66706dc --- /dev/null +++ b/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version:5.0 + +import PackageDescription + +let package = Package( + name: "DiffableDataSources", + platforms: [ + .iOS(.v9), .macOS(.v10_11), .tvOS(.v9) + ], + products: [ + .library(name: "DiffableDataSources", targets: ["DiffableDataSources"]) + ], + dependencies: [ + .package(url: "https://github.com/ra1028/DifferenceKit.git", .upToNextMinor(from: "1.1.0")) + ], + targets: [ + .target( + name: "DiffableDataSources", + dependencies: ["DifferenceKit"], + path: "Sources" + ), + .testTarget( + name: "DiffableDataSourcesTests", + dependencies: ["DiffableDataSources"], + path: "Tests" + ) + ], + swiftLanguageVersions: [.v5] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..4693651 --- /dev/null +++ b/README.md @@ -0,0 +1,208 @@ +

+DiffableDataSources +

+

+💾 A library for backporting UITableView/UICollectionViewDiffableDataSource
+powered by DifferenceKit. +

+ +

+Swift5 +Release +CocoaPods +Carthage +Swift Package Manager +
+Build Status +Platform +Lincense +

+ +

+Made with ❤️ by Ryo Aoyama +

+ +--- + +## Introduction + + + + +Apple has announced a diffable data source at WWDC 2019. +It's a great API that easily updating our table view and collection view items using automatic diffing. +However, it's a little while before we can use it in a production service. +That because it requires the latest OS to use. +DiffableDataSources make it possible to introduce almost the same functionality from now on. + +Uses a sophisticated open source [DifferenceKit](https://github.com/ra1028/DifferenceKit) for the algorithm engine. +It's extremely fast and completely avoids synchronization bugs, exceptions, and crashes. + +
+ +--- + +## Difference from the Official + +#### Spec + +- Supports iOS 9.0+ / macOS 10.11+ / tvOS 9.0+ +- Open sourced algorithm. +- Duplicate sections or items are allowed. +- Using `performBatchUpdates` for diffing updates. + +#### Namings + +`DiffableDataSources` have different class names to avoid conflicts with the official API. +Correspondence table is below. + +|Official |Backported | +|:---------------------------------------------------------------------------|:------------------------------------| +|[NSDiffableDataSourceSnapshot][NSDiffableDataSourceSnapshot_doc] |DiffableDataSourceSnapshot | +|[UITableViewDiffableDataSource][UITableViewDiffableDataSource_doc] |TableViewDiffableDataSource | +|[UICollectionViewDiffableDataSource][UICollectionViewDiffableDataSource_doc]|CollectionViewDiffableDataSource | +|[NSCollectionViewDiffableDataSource][NSCollectionViewDiffableDataSource_doc]|CocoaCollectionViewDiffableDataSource| + +[NSDiffableDataSourceSnapshot_doc]: https://developer.apple.com/documentation/uikit/uitableviewdiffabledatasource +[UITableViewDiffableDataSource_doc]: https://developer.apple.com/documentation/uikit/uitableviewdiffabledatasource +[UICollectionViewDiffableDataSource_doc]: https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource +[NSCollectionViewDiffableDataSource_doc]: https://developer.apple.com/documentation/appkit/nscollectionviewdiffabledatasource + +--- + +## Getting Started + +- [API Documentation](https://ra1028.github.io/DiffableDataSources) +- [Example Apps](https://github.com/ra1028/DiffableDataSources/tree/master/Examples) +- [WWDC 2019 Session](https://developer.apple.com/videos/play/wwdc2019/220) + +#### Build Project + +```sh +$ git clone https://github.com/ra1028/DiffableDataSources.git +$ cd DiffableDataSources/ +$ make setup +$ open DiffableDataSources.xcworkspace +``` + +--- + +## Basic Usage + +First, define the type representing section. +It should conforms to `Hashable` for identifies from the all sections. +Type of enum can used conveniently befause it conforms `Hashable` by default. + +```swift +enum Section { + case main +} +``` + +Then, define the item type conforms to `Hashable`. + +```swift +struct User: Hashable { + var name: String +} +``` + +Create a data source object, it will be set to table view automatically. +You should dequeue the non nil cells via closure. + +```swift +final class UsersViewController: UIViewController { + let tableView: UITableView = ... + + lazy var dataSource = TableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, user in + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + cell.textLabel?.text = user.name + return cell + } + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") + } +} +``` + +Manages and updates the data sources intuitively by intermediating `DiffableDataSourceSnapshot`. +The UI isn't updated until you apply the edited snapshot object. +Update the UI with diffing animation automatically calculated by applying an edited snapshot. + +```swift +let users = [ + User(name: "Steve Jobs"), + User(name: "Stephen Wozniak"), + User(name: "Tim Cook"), + User(name: "Jonathan Ive") +] + +let snapshot = DiffableDataSourceSnapshot() +snapshot.appendSections([.main]) +snapshot.appendItems(users) + +dataSource.apply(snapshot) +``` + +Check the documentation for more detailed API. + +

+[See More Usage] +

+ +--- + +## Requirements + +- Swift 5.0+ +- iOS 9.0+ +- macOS 10.11+ +- tvOS 9.0+ + +--- + +## Installation + +### [CocoaPods](https://cocoapods.org) +Add the following to your `Podfile`: +```ruby +pod 'DiffableDataSources' +``` + +### [Carthage](https://github.com/Carthage/Carthage) +Add the following to your `Cartfile`: +``` +github "ra1028/DiffableDataSources" +``` + +### [Swift Package Manager](https://swift.org/package-manager/) +Add the following to the dependencies of your `Package.swift`: +```swift +.package(url: "https://github.com/ra1028/DiffableDataSources.git", from: "x.x.x") +``` + +--- + +## Contributing + +Pull requests, bug reports and feature requests are welcome 🚀 +Please see the [CONTRIBUTING](https://github.com/ra1028/DiffableDataSources/blob/master/CONTRIBUTING.md) file for learn how to contribute to DiffableDataSources. + +--- + +## Relations + +#### [DifferenceKit](https://github.com/ra1028/DifferenceKit) +A fast and flexible O(n) difference algorithm framework for Swift collection. + +#### [Carbon](https://github.com/ra1028/Carbon) +A declarative library for building component-based user interfaces in UITableView and UICollectionView. + +--- + +## License + +DiffableDataSources is released under the [Apache 2.0 License](https://github.com/ra1028/DiffableDataSources/blob/master/LICENSE). diff --git a/Sources/AppKit/CocoaCollectionViewDiffableDataSource.swift b/Sources/AppKit/CocoaCollectionViewDiffableDataSource.swift new file mode 100644 index 0000000..bd6ed6b --- /dev/null +++ b/Sources/AppKit/CocoaCollectionViewDiffableDataSource.swift @@ -0,0 +1,111 @@ +#if os(macOS) + +import AppKit +import DifferenceKit + +/// A class for backporting `NSCollectionViewDiffableDataSource` introduced in macOS 10.15+. +/// Represents the data model object for `NSCollectionView` that can be applies the +/// changes with automatic diffing. +open class CocoaCollectionViewDiffableDataSource: NSObject, NSCollectionViewDataSource { + /// The type of closure providing the item. + public typealias ItemProvider = (NSCollectionView, IndexPath, ItemIdentifierType) -> NSCollectionViewItem? + + private weak var collectionView: NSCollectionView? + private let itemProvider: ItemProvider + private let core = DiffableDataSourceCore() + + /// Creates a new data source. + /// + /// - Parameters: + /// - collectionView: A collection view instance to be managed. + /// - itemProvider: A closure to make the item. + public init(collectionView: NSCollectionView, itemProvider: @escaping ItemProvider) { + self.collectionView = collectionView + self.itemProvider = itemProvider + super.init() + + collectionView.dataSource = self + } + + /// Applies given snapshot to perform automatic diffing update. + /// + /// - Parameters: + /// - snapshot: A snapshot object to be applied to data model. + /// - animatingDifferences: A Boolean value indicating whether to update with + /// diffing animation. + public func apply(_ snapshot: DiffableDataSourceSnapshot, animatingDifferences: Bool = true) { + core.apply( + snapshot, + view: collectionView, + animatingDifferences: animatingDifferences, + performUpdates: { collectionView, changeset, setSections in + collectionView.reload(using: changeset, setData: setSections) + }) + } + + /// Returns a new snapshot object of current state. + /// + /// - Returns: A new snapshot object of current state. + public func snapshot() -> DiffableDataSourceSnapshot { + return core.snapshot() + } + + /// Returns an item identifier for given index path. + /// + /// - Parameters: + /// - indexPath: An index path for the item identifier. + /// + /// - Returns: An item identifier for given index path. + public func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? { + return core.itemIdentifier(for: indexPath) + } + + /// Returns an index path for given item identifier. + /// + /// - Parameters: + /// - itemIdentifier: An identifier of item. + /// + /// - Returns: An index path for given item identifier. + public func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? { + return core.indexPath(for: itemIdentifier) + } + + /// Returns the number of sections in the data source. + /// + /// - Parameters: + /// - collectionView: A collection view instance managed by `self`. + /// + /// - Returns: The number of sections in the data source. + public func numberOfSections(in collectionView: NSCollectionView) -> Int { + return core.numberOfSections() + } + + /// Returns the number of items in the specified section. + /// + /// - Parameters: + /// - collectionView: A collection view instance managed by `self`. + /// - section: An index of section. + /// + /// - Returns: The number of items in the specified section. + public func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { + return core.numberOfItems(inSection: section) + } + + /// Returns an item at specified index path. + /// + /// - Parameters: + /// - collectionView: A collection view instance managed by `self`. + /// - indexPath: An index path for item. + /// + /// - Returns: An item at specified index path. + open func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { + let itemIdentifier = core.unsafeItemIdentifier(for: indexPath) + guard let item = itemProvider(collectionView, indexPath, itemIdentifier) else { + universalError("NSCollectionView dataSource returned a nil item at index path: \(indexPath), collectionView: \(collectionView), itemIdentifier: \(itemIdentifier)") + } + + return item + } +} + +#endif diff --git a/Sources/DiffableDataSourceSnapshot.swift b/Sources/DiffableDataSourceSnapshot.swift new file mode 100644 index 0000000..37f5ad1 --- /dev/null +++ b/Sources/DiffableDataSourceSnapshot.swift @@ -0,0 +1,204 @@ +/// A class for backporting `NSDiffableDataSourceSnapshot` introduced in iOS 13.0+, macOS 10.15+, tvOS 13.0+. +/// Represents the mutable state of diffable data source of UI. +public class DiffableDataSourceSnapshot { + internal let structure = SnapshotStructure() + + /// Creates a new empty snapshot object. + public init() {} + + /// The number of item identifiers in the snapshot. + public var numberOfItems: Int { + return itemIdentifiers.count + } + + /// The number of section identifiers in the snapshot. + public var numberOfSections: Int { + return sectionIdentifiers.count + } + + /// All section identifiers in the snapshot. + public var sectionIdentifiers: [SectionIdentifierType] { + return structure.allSectionIDs + } + + /// All item identifiers in the snapshot. + public var itemIdentifiers: [ItemIdentifierType] { + return structure.allItemIDs + } + + /// Returns the number of item identifiers in the specified section. + /// + /// - Parameters: + /// - identifier: An identifier of section. + /// + /// - Returns: The number of item identifiers in the specified section. + public func numberOfItems(inSection identifier: SectionIdentifierType) -> Int { + return itemIdentifiers(inSection: identifier).count + } + + /// Returns the item identifiers in the specified section. + /// + /// - Parameters: + /// - identifier: An identifier of section. + /// + /// - Returns: The item identifiers in the specified section. + public func itemIdentifiers(inSection identifier: SectionIdentifierType) -> [ItemIdentifierType] { + return structure.items(in: identifier) + } + + /// Returns a section identifier containing the specified item. + /// + /// - Parameters: + /// - identifier: An identifier of item. + /// + /// - Returns: A section identifier containing the specified item. + public func sectionIdentifier(containingItem identifier: ItemIdentifierType) -> SectionIdentifierType? { + return structure.section(containing: identifier) + } + + /// Returns an index of the specified item. + /// + /// - Parameters: + /// - identifier: An identifier of item. + /// + /// - Returns: An index of the specified item. + public func indexOfItem(_ identifier: ItemIdentifierType) -> Int? { + return itemIdentifiers.firstIndex { $0.isEqualHash(to: identifier) } + } + + /// Returns an index of the specified section. + /// + /// - Parameters: + /// - identifier: An identifier of section. + /// + /// - Returns: An index of the specified section. + public func indexOfSection(_ identifier: SectionIdentifierType) -> Int? { + return sectionIdentifiers.firstIndex { $0.isEqualHash(to: identifier) } + } + + /// Appends the given item identifiers to the specified section or last section. + /// + /// - Parameters: + /// - identifiers: The item identifiers to be appended. + /// - sectionIdentifier: An identifier of section to append the given identiciers. + public func appendItems(_ identifiers: [ItemIdentifierType], toSection sectionIdentifier: SectionIdentifierType? = nil) { + structure.append(itemIDs: identifiers, to: sectionIdentifier) + } + + /// Inserts the given item identifiers before the specified item. + /// + /// - Parameters: + /// - identifiers: The item identifiers to be inserted. + /// - beforeIdentifier: An identifier of item. + public func insertItems(_ identifiers: [ItemIdentifierType], beforeItem beforeIdentifier: ItemIdentifierType) { + structure.insert(itemIDs: identifiers, before: beforeIdentifier) + } + + /// Inserts the given item identifiers after the specified item. + /// + /// - Parameters: + /// - identifiers: The item identifiers to be inserted. + /// - afterIdentifier: An identifier of item. + public func insertItems(_ identifiers: [ItemIdentifierType], afterItem afterIdentifier: ItemIdentifierType) { + structure.insert(itemIDs: identifiers, after: afterIdentifier) + } + + /// Deletes the specified items. + /// + /// - Parameters: + /// - identifiers: The item identifiers to be deleted. + public func deleteItems(_ identifiers: [ItemIdentifierType]) { + structure.remove(itemIDs: identifiers) + } + + /// Deletes the all items in the snapshot. + public func deleteAllItems() { + structure.removeAllItems() + } + + /// Moves the given item identifier before the specified item. + /// + /// - Parameters: + /// - identifier: An item identifier to be moved. + /// - toIdentifier: An identifier of item. + public func moveItem(_ identifier: ItemIdentifierType, beforeItem toIdentifier: ItemIdentifierType) { + structure.move(itemID: identifier, before: toIdentifier) + } + + /// Moves the given item identifier after the specified item. + /// + /// - Parameters: + /// - identifier: An item identifier to be moved. + /// - toIdentifier: An identifier of item. + public func moveItem(_ identifier: ItemIdentifierType, afterItem toIdentifier: ItemIdentifierType) { + structure.move(itemID: identifier, after: toIdentifier) + } + + /// Reloads the specified items. + /// + /// - Parameters: + /// - identifiers: The item identifiers to be reloaded. + public func reloadItems(_ identifiers: [ItemIdentifierType]) { + structure.update(itemIDs: identifiers) + } + + /// Appends the given section identifiers to the end of sections. + /// + /// - Parameters: + /// - identifiers: The section identifiers to be appended. + public func appendSections(_ identifiers: [SectionIdentifierType]) { + structure.append(sectionIDs: identifiers) + } + + /// Inserts the given section identifiers before the specified section. + /// + /// - Parameters: + /// - identifiers: The section identifiers to be inserted. + /// - toIdentifier: An identifier of setion. + public func insertSections(_ identifiers: [SectionIdentifierType], beforeSection toIdentifier: SectionIdentifierType) { + structure.insert(sectionIDs: identifiers, before: toIdentifier) + } + + /// Inserts the given section identifiers after the specified section. + /// + /// - Parameters: + /// - identifiers: The section identifiers to be inserted. + /// - toIdentifier: An identifier of setion. + public func insertSections(_ identifiers: [SectionIdentifierType], afterSection toIdentifier: SectionIdentifierType) { + structure.insert(sectionIDs: identifiers, after: toIdentifier) + } + + /// Deletes the specified sections. + /// + /// - Parameters: + /// - identifiers: The section identifiers to be deleted. + public func deleteSections(_ identifiers: [SectionIdentifierType]) { + structure.remove(sectionIDs: identifiers) + } + + /// Moves the given section identifier before the specified section. + /// + /// - Parameters: + /// - identifier: A section identifier to be moved. + /// - toIdentifier: An identifier of section. + public func moveSection(_ identifier: SectionIdentifierType, beforeSection toIdentifier: SectionIdentifierType) { + structure.move(sectionID: identifier, before: toIdentifier) + } + + /// Moves the given section identifier after the specified section. + /// + /// - Parameters: + /// - identifier: A section identifier to be moved. + /// - toIdentifier: An identifier of section. + public func moveSection(_ identifier: SectionIdentifierType, afterSection toIdentifier: SectionIdentifierType) { + structure.move(sectionID: identifier, after: toIdentifier) + } + + /// Reloads the specified sections. + /// + /// - Parameters: + /// - identifiers: The section identifiers to be reloaded. + public func reloadSections(_ identifiers: [SectionIdentifierType]) { + structure.update(sectionIDs: identifiers) + } +} diff --git a/Sources/Info.plist b/Sources/Info.plist new file mode 100644 index 0000000..e1fe4cf --- /dev/null +++ b/Sources/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/Sources/Internal/DiffableDataSourceCore.swift b/Sources/Internal/DiffableDataSourceCore.swift new file mode 100644 index 0000000..4349a94 --- /dev/null +++ b/Sources/Internal/DiffableDataSourceCore.swift @@ -0,0 +1,98 @@ +import Foundation +import QuartzCore +import DifferenceKit + +final class DiffableDataSourceCore { + typealias Section = SnapshotStructure.Section + + private let dispatcher = MainThreadSerialDispatcher() + private var currentSnapshot = DiffableDataSourceSnapshot() + private var sections: [Section] = [] + + func apply( + _ snapshot: DiffableDataSourceSnapshot, + view: View?, + animatingDifferences: Bool, + performUpdates: @escaping (View, StagedChangeset<[Section]>, @escaping ([Section]) -> Void) -> Void + ) { + dispatcher.dispatch { [weak self] in + guard let self = self else { + return + } + + self.currentSnapshot = snapshot + + let newSections = snapshot.structure.sections + + guard let view = view else { + return self.sections = newSections + } + + func performDiffingUpdates() { + let changeset = StagedChangeset(source: self.sections, target: newSections) + performUpdates(view, changeset) { sections in + self.sections = sections + } + } + + if animatingDifferences { + performDiffingUpdates() + } + else { + CATransaction.begin() + CATransaction.setDisableActions(true) + performDiffingUpdates() + CATransaction.commit() + } + } + } + + func snapshot() -> DiffableDataSourceSnapshot { + let snapshot = DiffableDataSourceSnapshot() + snapshot.structure.sections = currentSnapshot.structure.sections + return snapshot + } + + func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? { + guard 0.. ItemIdentifierType { + guard let itemIdentifier = itemIdentifier(for: indexPath) else { + universalError("Item not found at the specified index path(\(indexPath)).") + } + + return itemIdentifier + } + + func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? { + let indexPathMap: [ItemIdentifierType: IndexPath] = sections.enumerated() + .reduce(into: [:]) { result, section in + for (itemIndex, item) in section.element.elements.enumerated() { + result[item.differenceIdentifier] = IndexPath( + item: itemIndex, + section: section.offset + ) + } + } + return indexPathMap[itemIdentifier] + } + + func numberOfSections() -> Int { + return sections.count + } + + func numberOfItems(inSection section: Int) -> Int { + return sections[section].elements.count + } +} diff --git a/Sources/Internal/HashableExtension.swift b/Sources/Internal/HashableExtension.swift new file mode 100644 index 0000000..5e72488 --- /dev/null +++ b/Sources/Internal/HashableExtension.swift @@ -0,0 +1,5 @@ +extension Hashable { + func isEqualHash(to other: Self) -> Bool { + return hashValue == other.hashValue && self == other + } +} diff --git a/Sources/Internal/MainThreadSerialDispatcher.swift b/Sources/Internal/MainThreadSerialDispatcher.swift new file mode 100644 index 0000000..df43e45 --- /dev/null +++ b/Sources/Internal/MainThreadSerialDispatcher.swift @@ -0,0 +1,33 @@ +import Foundation + +final class MainThreadSerialDispatcher { + private let executingCount = UnsafeMutablePointer.allocate(capacity: 1) + + init() { + executingCount.initialize(to: 0) + } + + deinit { + executingCount.deinitialize(count: 1) + executingCount.deallocate() + } + + func dispatch(_ action: @escaping () -> Void) { + let count = OSAtomicIncrement32(executingCount) + + if Thread.isMainThread && count == 1 { + action() + OSAtomicDecrement32(executingCount) + } + else { + DispatchQueue.main.async { [weak self] in + guard let self = self else { + return + } + + action() + OSAtomicDecrement32(self.executingCount) + } + } + } +} diff --git a/Sources/Internal/SnapshotStructure.swift b/Sources/Internal/SnapshotStructure.swift new file mode 100644 index 0000000..4a6ee61 --- /dev/null +++ b/Sources/Internal/SnapshotStructure.swift @@ -0,0 +1,294 @@ +import Foundation +import DifferenceKit + +final class SnapshotStructure { + struct Item: Differentiable, Equatable { + var differenceIdentifier: ItemID + var isReloaded: Bool + + init(id: ItemID, isReloaded: Bool) { + self.differenceIdentifier = id + self.isReloaded = isReloaded + } + + init(id: ItemID) { + self.init(id: id, isReloaded: false) + } + + func isContentEqual(to source: Item) -> Bool { + return !isReloaded && differenceIdentifier == source.differenceIdentifier + } + } + + struct Section: DifferentiableSection, Equatable { + var differenceIdentifier: SectionID + var elements: [Item] = [] + var isReloaded: Bool + + init(id: SectionID, items: [Item], isReloaded: Bool) { + self.differenceIdentifier = id + self.elements = items + self.isReloaded = isReloaded + } + + init(id: SectionID) { + self.init(id: id, items: [], isReloaded: false) + } + + init(source: Section, elements: C) where C.Element == Item { + self.init(id: source.differenceIdentifier, items: Array(elements), isReloaded: source.isReloaded) + } + + func isContentEqual(to source: Section) -> Bool { + return !isReloaded && differenceIdentifier == source.differenceIdentifier + } + } + + var sections: [Section] = [] + + var allSectionIDs: [SectionID] { + return sections.map { $0.differenceIdentifier } + } + + var allItemIDs: [ItemID] { + return sections.lazy + .flatMap { $0.elements } + .map { $0.differenceIdentifier } + } + + func items(in sectionID: SectionID, file: StaticString = #file, line: UInt = #line) -> [ItemID] { + guard let sectionIndex = sectionIndex(of: sectionID) else { + specifiedSectionIsNotFound(sectionID, file: file, line: line) + } + + return sections[sectionIndex].elements.map { $0.differenceIdentifier } + } + + func section(containing itemID: ItemID) -> SectionID? { + return itemPositionMap()[itemID]?.section.differenceIdentifier + } + + func append(itemIDs: [ItemID], to sectionID: SectionID? = nil, file: StaticString = #file, line: UInt = #line) { + let index: Array
.Index + + if let sectionID = sectionID { + guard let sectionIndex = sectionIndex(of: sectionID) else { + specifiedSectionIsNotFound(sectionID, file: file, line: line) + } + + index = sectionIndex + } + else { + guard !sections.isEmpty else { + thereAreCurrentlyNoSections(file: file, line: line) + } + + index = sections.index(before: sections.endIndex) + } + + let items = itemIDs.lazy.map(Item.init) + sections[index].elements.append(contentsOf: items) + } + + func insert(itemIDs: [ItemID], before beforeItemID: ItemID, file: StaticString = #file, line: UInt = #line) { + guard let itemPosition = itemPositionMap()[beforeItemID] else { + specifiedItemIsNotFound(beforeItemID, file: file, line: line) + } + + let items = itemIDs.lazy.map(Item.init) + sections[itemPosition.sectionIndex].elements.insert(contentsOf: items, at: itemPosition.itemRelativeIndex) + } + + func insert(itemIDs: [ItemID], after afterItemID: ItemID, file: StaticString = #file, line: UInt = #line) { + guard let itemPosition = itemPositionMap()[afterItemID] else { + specifiedItemIsNotFound(afterItemID, file: file, line: line) + } + + let itemIndex = sections[itemPosition.sectionIndex].elements.index(after: itemPosition.itemRelativeIndex) + let items = itemIDs.lazy.map(Item.init) + sections[itemPosition.sectionIndex].elements.insert(contentsOf: items, at: itemIndex) + } + + func remove(itemIDs: [ItemID]) { + let itemPositionMap = self.itemPositionMap() + var removeIndexSetMap = [Int: IndexSet]() + + for itemID in itemIDs { + guard let itemPosition = itemPositionMap[itemID] else { + continue + } + + removeIndexSetMap[itemPosition.sectionIndex, default: []].insert(itemPosition.itemRelativeIndex) + } + + for (sectionIndex, removeIndexSet) in removeIndexSetMap { + for range in removeIndexSet.rangeView.reversed() { + sections[sectionIndex].elements.removeSubrange(range) + } + } + } + + func removeAllItems() { + for sectionIndex in sections.indices { + sections[sectionIndex].elements.removeAll() + } + } + + func move(itemID: ItemID, before beforeItemID: ItemID, file: StaticString = #file, line: UInt = #line) { + guard let removed = remove(itemID: itemID) else { + specifiedItemIsNotFound(itemID, file: file, line: line) + } + + guard let itemPosition = itemPositionMap()[beforeItemID] else { + specifiedItemIsNotFound(beforeItemID, file: file, line: line) + } + + sections[itemPosition.sectionIndex].elements.insert(removed, at: itemPosition.itemRelativeIndex) + } + + func move(itemID: ItemID, after afterItemID: ItemID, file: StaticString = #file, line: UInt = #line) { + guard let removed = remove(itemID: itemID) else { + specifiedItemIsNotFound(itemID, file: file, line: line) + } + + guard let itemPosition = itemPositionMap()[afterItemID] else { + specifiedItemIsNotFound(afterItemID, file: file, line: line) + } + + let itemIndex = sections[itemPosition.sectionIndex].elements.index(after: itemPosition.itemRelativeIndex) + sections[itemPosition.sectionIndex].elements.insert(removed, at: itemIndex) + } + + func update(itemIDs: [ItemID], file: StaticString = #file, line: UInt = #line) { + let itemPositionMap = self.itemPositionMap() + + for itemID in itemIDs { + guard let itemPosition = itemPositionMap[itemID] else { + specifiedItemIsNotFound(itemID, file: file, line: line) + } + + sections[itemPosition.sectionIndex].elements[itemPosition.itemRelativeIndex].isReloaded = true + } + } + + func append(sectionIDs: [SectionID]) { + let newSections = sectionIDs.lazy.map(Section.init) + sections.append(contentsOf: newSections) + } + + func insert(sectionIDs: [SectionID], before beforeSectionID: SectionID, file: StaticString = #file, line: UInt = #line) { + guard let sectionIndex = sectionIndex(of: beforeSectionID) else { + specifiedSectionIsNotFound(beforeSectionID, file: file, line: line) + } + + let newSections = sectionIDs.lazy.map(Section.init) + sections.insert(contentsOf: newSections, at: sectionIndex) + } + + func insert(sectionIDs: [SectionID], after afterSectionID: SectionID, file: StaticString = #file, line: UInt = #line) { + guard let beforeIndex = sectionIndex(of: afterSectionID) else { + specifiedSectionIsNotFound(afterSectionID, file: file, line: line) + } + + let sectionIndex = sections.index(after: beforeIndex) + let newSections = sectionIDs.lazy.map(Section.init) + sections.insert(contentsOf: newSections, at: sectionIndex) + } + + func remove(sectionIDs: [SectionID]) { + for sectionID in sectionIDs { + remove(sectionID: sectionID) + } + } + + func move(sectionID: SectionID, before beforeSectionID: SectionID, file: StaticString = #file, line: UInt = #line) { + guard let removed = remove(sectionID: sectionID) else { + specifiedSectionIsNotFound(sectionID, file: file, line: line) + } + + guard let sectionIndex = sectionIndex(of: beforeSectionID) else { + specifiedSectionIsNotFound(beforeSectionID, file: file, line: line) + } + + sections.insert(removed, at: sectionIndex) + } + + func move(sectionID: SectionID, after afterSectionID: SectionID, file: StaticString = #file, line: UInt = #line) { + guard let removed = remove(sectionID: sectionID) else { + specifiedSectionIsNotFound(sectionID, file: file, line: line) + } + + guard let beforeIndex = sectionIndex(of: afterSectionID) else { + specifiedSectionIsNotFound(afterSectionID, file: file, line: line) + } + + let sectionIndex = sections.index(after: beforeIndex) + sections.insert(removed, at: sectionIndex) + } + + func update(sectionIDs: [SectionID]) { + for sectionID in sectionIDs { + guard let sectionIndex = sectionIndex(of: sectionID) else { + continue + } + + sections[sectionIndex].isReloaded = true + } + } +} + +private extension SnapshotStructure { + struct ItemPosition { + var item: Item + var itemRelativeIndex: Int + var section: Section + var sectionIndex: Int + } + + func sectionIndex(of sectionID: SectionID) -> Array
.Index? { + return sections.firstIndex { $0.differenceIdentifier.isEqualHash(to: sectionID) } + } + + @discardableResult + func remove(itemID: ItemID) -> Item? { + guard let itemPosition = itemPositionMap()[itemID] else { + return nil + } + + return sections[itemPosition.sectionIndex].elements.remove(at: itemPosition.itemRelativeIndex) + } + + @discardableResult + func remove(sectionID: SectionID) -> Section? { + guard let sectionIndex = sectionIndex(of: sectionID) else { + return nil + } + + return sections.remove(at: sectionIndex) + } + + func itemPositionMap() -> [ItemID: ItemPosition] { + return sections.enumerated().reduce(into: [:]) { result, section in + for (itemRelativeIndex, item) in section.element.elements.enumerated() { + result[item.differenceIdentifier] = ItemPosition( + item: item, + itemRelativeIndex: itemRelativeIndex, + section: section.element, + sectionIndex: section.offset + ) + } + } + } + + func specifiedItemIsNotFound(_ id: ItemID, file: StaticString, line: UInt) -> Never { + universalError("Specified item\(id) is not found.", file: file, line: line) + } + + func specifiedSectionIsNotFound(_ id: SectionID, file: StaticString, line: UInt) -> Never { + universalError("Specified section\(id) is not found.", file: file, line: line) + } + + func thereAreCurrentlyNoSections(file: StaticString, line: UInt) -> Never { + universalError("There are currently no sections.", file: file, line: line) + } +} diff --git a/Sources/Internal/UniversalError.swift b/Sources/Internal/UniversalError.swift new file mode 100644 index 0000000..2a6fc00 --- /dev/null +++ b/Sources/Internal/UniversalError.swift @@ -0,0 +1,3 @@ +func universalError(_ message: String, file: StaticString = #file, line: UInt = #line) -> Never { + fatalError("[DiffableDataSources] \(message)", file: file, line: line) +} diff --git a/Sources/UIKit/CollectionViewDiffableDataSource.swift b/Sources/UIKit/CollectionViewDiffableDataSource.swift new file mode 100644 index 0000000..d978f6d --- /dev/null +++ b/Sources/UIKit/CollectionViewDiffableDataSource.swift @@ -0,0 +1,133 @@ +#if os(iOS) || os(tvOS) + +import UIKit +import DifferenceKit + +/// A class for backporting `UICollectionViewDiffableDataSource` introduced in iOS 13.0+, tvOS 13.0+. +/// Represents the data model object for `UICollectionView` that can be applies the +/// changes with automatic diffing. +open class CollectionViewDiffableDataSource: NSObject, UICollectionViewDataSource { + /// The type of closure providing the cell. + public typealias CellProvider = (UICollectionView, IndexPath, ItemIdentifierType) -> UICollectionViewCell? + + /// The type of closure providing the supplementary view for element of kind. + public typealias SupplementaryViewProvider = (UICollectionView, String, IndexPath) -> UICollectionReusableView? + + /// A closure to dequeue the views for element of kind. + public var supplementaryViewProvider: SupplementaryViewProvider? + + private weak var collectionView: UICollectionView? + private let cellProvider: CellProvider + private let core = DiffableDataSourceCore() + + /// Creates a new data source. + /// + /// - Parameters: + /// - collectionView: A collection view instance to be managed. + /// - cellProvider: A closure to dequeue the cell for items. + public init(collectionView: UICollectionView, cellProvider: @escaping CellProvider) { + self.collectionView = collectionView + self.cellProvider = cellProvider + super.init() + + collectionView.dataSource = self + } + + /// Applies given snapshot to perform automatic diffing update. + /// + /// - Parameters: + /// - snapshot: A snapshot object to be applied to data model. + /// - animatingDifferences: A Boolean value indicating whether to update with + /// diffing animation. + public func apply(_ snapshot: DiffableDataSourceSnapshot, animatingDifferences: Bool = true) { + core.apply( + snapshot, + view: collectionView, + animatingDifferences: animatingDifferences, + performUpdates: { collectionView, changeset, setSections in + collectionView.reload(using: changeset, setData: setSections) + }) + } + + /// Returns a new snapshot object of current state. + /// + /// - Returns: A new snapshot object of current state. + public func snapshot() -> DiffableDataSourceSnapshot { + return core.snapshot() + } + + /// Returns an item identifier for given index path. + /// + /// - Parameters: + /// - indexPath: An index path for the item identifier. + /// + /// - Returns: An item identifier for given index path. + public func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? { + return core.itemIdentifier(for: indexPath) + } + + /// Returns an index path for given item identifier. + /// + /// - Parameters: + /// - itemIdentifier: An identifier of item. + /// + /// - Returns: An index path for given item identifier. + public func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? { + return core.indexPath(for: itemIdentifier) + } + + /// Returns the number of sections in the data source. + /// + /// - Parameters: + /// - collectionView: A collection view instance managed by `self`. + /// + /// - Returns: The number of sections in the data source. + public func numberOfSections(in collectionView: UICollectionView) -> Int { + return core.numberOfSections() + } + + /// Returns the number of items in the specified section. + /// + /// - Parameters: + /// - collectionView: A collection view instance managed by `self`. + /// - section: An index of section. + /// + /// - Returns: The number of items in the specified section. + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return core.numberOfItems(inSection: section) + } + + /// Returns a cell for item at specified index path. + /// + /// - Parameters: + /// - collectionView: A collection view instance managed by `self`. + /// - indexPath: An index path for cell. + /// + /// - Returns: A cell for row at specified index path. + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let itemIdentifier = core.unsafeItemIdentifier(for: indexPath) + guard let cell = cellProvider(collectionView, indexPath, itemIdentifier) else { + universalError("UICollectionView dataSource returned a nil cell for item at index path: \(indexPath), collectionView: \(collectionView), itemIdentifier: \(itemIdentifier)") + } + + return cell + } + + /// Returns a supplementary view for element of kind at specified index path. + /// + /// - Parameters: + /// - collectionView: A collection view instance managed by `self`. + /// - kind: The kind of element to be display. + /// - indexPath: An index path for supplementary view. + /// + /// - Returns: A supplementary view for element of kind at specified index path. + open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + guard let view = supplementaryViewProvider?(collectionView, kind, indexPath) else { + return UICollectionReusableView() + } + + return view + } +} + +#endif diff --git a/Sources/UIKit/TableViewDiffableDataSource.swift b/Sources/UIKit/TableViewDiffableDataSource.swift new file mode 100644 index 0000000..12c4c17 --- /dev/null +++ b/Sources/UIKit/TableViewDiffableDataSource.swift @@ -0,0 +1,114 @@ +#if os(iOS) || os(tvOS) + +import UIKit +import DifferenceKit + +/// A class for backporting `UITableViewDiffableDataSource` introduced in iOS 13.0+, tvOS 13.0+. +/// Represents the data model object for `UITableView` that can be applies the +/// changes with automatic diffing. +open class TableViewDiffableDataSource: NSObject, UITableViewDataSource { + /// The type of closure providing the cell. + public typealias CellProvider = (UITableView, IndexPath, ItemIdentifierType) -> UITableViewCell? + + /// The default animation to updating the views. + public var defaultRowAnimation: UITableView.RowAnimation = .automatic + + private weak var tableView: UITableView? + private let cellProvider: CellProvider + private let core = DiffableDataSourceCore() + + /// Creates a new data source. + /// + /// - Parameters: + /// - tableView: A table view instance to be managed. + /// - cellProvider: A closure to dequeue the cell for rows. + public init(tableView: UITableView, cellProvider: @escaping CellProvider) { + self.tableView = tableView + self.cellProvider = cellProvider + super.init() + + tableView.dataSource = self + } + + /// Applies given snapshot to perform automatic diffing update. + /// + /// - Parameters: + /// - snapshot: A snapshot object to be applied to data model. + /// - animatingDifferences: A Boolean value indicating whether to update with + /// diffing animation. + public func apply(_ snapshot: DiffableDataSourceSnapshot, animatingDifferences: Bool = true) { + core.apply( + snapshot, + view: tableView, + animatingDifferences: animatingDifferences, + performUpdates: { tableView, changeset, setSections in + tableView.reload(using: changeset, with: self.defaultRowAnimation, setData: setSections) + }) + } + + /// Returns a new snapshot object of current state. + /// + /// - Returns: A new snapshot object of current state. + public func snapshot() -> DiffableDataSourceSnapshot { + return core.snapshot() + } + + /// Returns an item identifier for given index path. + /// + /// - Parameters: + /// - indexPath: An index path for the item identifier. + /// + /// - Returns: An item identifier for given index path. + public func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? { + return core.itemIdentifier(for: indexPath) + } + + /// Returns an index path for given item identifier. + /// + /// - Parameters: + /// - itemIdentifier: An identifier of item. + /// + /// - Returns: An index path for given item identifier. + public func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? { + return core.indexPath(for: itemIdentifier) + } + + /// Returns the number of sections in the data source. + /// + /// - Parameters: + /// - tableView: A table view instance managed by `self`. + /// + /// - Returns: The number of sections in the data source. + public func numberOfSections(in tableView: UITableView) -> Int { + return core.numberOfSections() + } + + /// Returns the number of items in the specified section. + /// + /// - Parameters: + /// - tableView: A table view instance managed by `self`. + /// - section: An index of section. + /// + /// - Returns: The number of items in the specified section. + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return core.numberOfItems(inSection: section) + } + + /// Returns a cell for row at specified index path. + /// + /// - Parameters: + /// - tableView: A table view instance managed by `self`. + /// - indexPath: An index path for cell. + /// + /// - Returns: A cell for row at specified index path. + open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let itemIdentifier = core.unsafeItemIdentifier(for: indexPath) + guard let cell = cellProvider(tableView, indexPath, itemIdentifier) else { + universalError("UITableView dataSource returned a nil cell for row at index path: \(indexPath), tableView: \(tableView), itemIdentifier: \(itemIdentifier)") + } + + return cell + } +} + +#endif diff --git a/Tests/CocoaCollectionViewDiffableDataSourceTests.swift b/Tests/CocoaCollectionViewDiffableDataSourceTests.swift new file mode 100644 index 0000000..438a7eb --- /dev/null +++ b/Tests/CocoaCollectionViewDiffableDataSourceTests.swift @@ -0,0 +1,185 @@ +#if os(macOS) + +import XCTest +import AppKit +@testable import DiffableDataSources + +final class CocoaCollectionViewDiffableDataSourceTests: XCTestCase { + func testInit() { + let collectionView = MockCollectionView() + let dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + NSCollectionViewItem() + } + + XCTAssertTrue(collectionView.dataSource === dataSource) + } + + func testApply() { + let collectionView = MockCollectionView() + let dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + NSCollectionViewItem() + } + + let snapshot = DiffableDataSourceSnapshot() + + dataSource.apply(snapshot) + XCTAssertEqual(collectionView.isPerformBatchUpdatesCalledCount, 0) + + snapshot.appendSections([0]) + snapshot.appendItems([0]) + + dataSource.apply(snapshot) + XCTAssertEqual(collectionView.isPerformBatchUpdatesCalledCount, 1) + + dataSource.apply(snapshot) + XCTAssertEqual(collectionView.isPerformBatchUpdatesCalledCount, 1) + + snapshot.appendItems([1]) + + dataSource.apply(snapshot) + XCTAssertEqual(collectionView.isPerformBatchUpdatesCalledCount, 2) + } + + func testSnapshot() { + let collectionView = MockCollectionView() + let dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + NSCollectionViewItem() + } + + let snapshot1 = dataSource.snapshot() + XCTAssertEqual(snapshot1.sectionIdentifiers, []) + XCTAssertEqual(snapshot1.itemIdentifiers, []) + + dataSource.snapshot().appendSections([0, 1, 2]) + let snapshot2 = dataSource.snapshot() + XCTAssertEqual(snapshot2.sectionIdentifiers, []) + XCTAssertEqual(snapshot2.itemIdentifiers, []) + + let snapshotToApply = DiffableDataSourceSnapshot() + snapshotToApply.appendSections([0, 1, 2]) + snapshotToApply.appendItems([0, 1, 2]) + dataSource.apply(snapshotToApply) + + let snapshot3 = dataSource.snapshot() + XCTAssertEqual(snapshot3.sectionIdentifiers, [0, 1, 2]) + XCTAssertEqual(snapshot3.itemIdentifiers, [0, 1, 2]) + + dataSource.snapshot().appendSections([3, 4, 5]) + + let snapshot4 = dataSource.snapshot() + XCTAssertEqual(snapshot4.sectionIdentifiers, [0, 1, 2]) + XCTAssertEqual(snapshot4.itemIdentifiers, [0, 1, 2]) + + snapshot4.appendSections([3, 4, 5]) + snapshot4.appendItems([3, 4, 5]) + dataSource.apply(snapshot4) + + let snapshot5 = dataSource.snapshot() + XCTAssertEqual(snapshot5.sectionIdentifiers, [0, 1, 2, 3, 4, 5]) + XCTAssertEqual(snapshot5.itemIdentifiers, [0, 1, 2, 3, 4, 5]) + } + + func testItemIdentifier() { + let collectionView = MockCollectionView() + let dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + NSCollectionViewItem() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.itemIdentifier(for: IndexPath(item: 1, section: 0)), 1) + XCTAssertEqual(dataSource.itemIdentifier(for: IndexPath(item: 100, section: 100)), nil) + } + + func testIndexPath() { + let collectionView = MockCollectionView() + let dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + NSCollectionViewItem() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.indexPath(for: 2), IndexPath(item: 2, section: 0)) + XCTAssertEqual(dataSource.indexPath(for: 100), nil) + } + + func testNumberOfSections() { + let collectionView = MockCollectionView() + let dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + NSCollectionViewItem() + } + + XCTAssertEqual(dataSource.numberOfSections(in: collectionView), 0) + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.numberOfSections(in: collectionView), 3) + } + + func testNumberOfRowsInSection() { + let collectionView = MockCollectionView() + let dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + NSCollectionViewItem() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.collectionView(collectionView, numberOfItemsInSection: 0), 3) + } + + func testCellForRowAt() { + let collectionView = MockCollectionView() + let item = NSCollectionViewItem() + let dataSource = CocoaCollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + item + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual( + dataSource.collectionView(collectionView, itemForRepresentedObjectAt: IndexPath(item: 1, section: 0)), + item + ) + } +} + +final class MockCollectionView: NSCollectionView { + var isPerformBatchUpdatesCalledCount = 0 + + init() { + super.init(frame: .zero) + + let window = NSWindow() + window.contentView = self + } + + @available(*, unavailable) + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func performBatchUpdates(_ updates: (() -> Void)?, completionHandler: ((Bool) -> Void)? = nil) { + isPerformBatchUpdatesCalledCount += 1 + updates?() + completionHandler?(true) + } + + override func insertItems(at indexPaths: Set) {} +} + +#endif diff --git a/Tests/CollectionViewDiffableDataSourceTests.swift b/Tests/CollectionViewDiffableDataSourceTests.swift new file mode 100644 index 0000000..c0a17e0 --- /dev/null +++ b/Tests/CollectionViewDiffableDataSourceTests.swift @@ -0,0 +1,186 @@ +#if os(iOS) || os(tvOS) + +import XCTest +import UIKit +@testable import DiffableDataSources + +final class CollectionViewDiffableDataSourceTests: XCTestCase { + func testInit() { + let collectionView = MockCollectionView() + let dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + UICollectionViewCell() + } + + XCTAssertTrue(collectionView.dataSource === dataSource) + } + + func testApply() { + let collectionView = MockCollectionView() + let dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + UICollectionViewCell() + } + + let snapshot = DiffableDataSourceSnapshot() + + dataSource.apply(snapshot) + XCTAssertEqual(collectionView.isPerformBatchUpdatesCalledCount, 0) + + snapshot.appendSections([0]) + snapshot.appendItems([0]) + + dataSource.apply(snapshot) + XCTAssertEqual(collectionView.isPerformBatchUpdatesCalledCount, 1) + + dataSource.apply(snapshot) + XCTAssertEqual(collectionView.isPerformBatchUpdatesCalledCount, 1) + + snapshot.appendItems([1]) + + dataSource.apply(snapshot) + XCTAssertEqual(collectionView.isPerformBatchUpdatesCalledCount, 2) + } + + func testSnapshot() { + let collectionView = MockCollectionView() + let dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + UICollectionViewCell() + } + + let snapshot1 = dataSource.snapshot() + XCTAssertEqual(snapshot1.sectionIdentifiers, []) + XCTAssertEqual(snapshot1.itemIdentifiers, []) + + dataSource.snapshot().appendSections([0, 1, 2]) + let snapshot2 = dataSource.snapshot() + XCTAssertEqual(snapshot2.sectionIdentifiers, []) + XCTAssertEqual(snapshot2.itemIdentifiers, []) + + let snapshotToApply = DiffableDataSourceSnapshot() + snapshotToApply.appendSections([0, 1, 2]) + snapshotToApply.appendItems([0, 1, 2]) + dataSource.apply(snapshotToApply) + + let snapshot3 = dataSource.snapshot() + XCTAssertEqual(snapshot3.sectionIdentifiers, [0, 1, 2]) + XCTAssertEqual(snapshot3.itemIdentifiers, [0, 1, 2]) + + dataSource.snapshot().appendSections([3, 4, 5]) + + let snapshot4 = dataSource.snapshot() + XCTAssertEqual(snapshot4.sectionIdentifiers, [0, 1, 2]) + XCTAssertEqual(snapshot4.itemIdentifiers, [0, 1, 2]) + + snapshot4.appendSections([3, 4, 5]) + snapshot4.appendItems([3, 4, 5]) + dataSource.apply(snapshot4) + + let snapshot5 = dataSource.snapshot() + XCTAssertEqual(snapshot5.sectionIdentifiers, [0, 1, 2, 3, 4, 5]) + XCTAssertEqual(snapshot5.itemIdentifiers, [0, 1, 2, 3, 4, 5]) + } + + func testItemIdentifier() { + let collectionView = MockCollectionView() + let dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + UICollectionViewCell() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.itemIdentifier(for: IndexPath(item: 1, section: 0)), 1) + XCTAssertEqual(dataSource.itemIdentifier(for: IndexPath(item: 100, section: 100)), nil) + } + + func testIndexPath() { + let collectionView = MockCollectionView() + let dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + UICollectionViewCell() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.indexPath(for: 2), IndexPath(item: 2, section: 0)) + XCTAssertEqual(dataSource.indexPath(for: 100), nil) + } + + func testNumberOfSections() { + let collectionView = MockCollectionView() + let dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + UICollectionViewCell() + } + + XCTAssertEqual(dataSource.numberOfSections(in: collectionView), 0) + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.numberOfSections(in: collectionView), 3) + } + + func testNumberOfRowsInSection() { + let collectionView = MockCollectionView() + let dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + UICollectionViewCell() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.collectionView(collectionView, numberOfItemsInSection: 0), 3) + } + + func testCellForRowAt() { + let collectionView = MockCollectionView() + let cell = UICollectionViewCell() + let dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { _, _, _ in + cell + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual( + dataSource.collectionView(collectionView, cellForItemAt: IndexPath(item: 1, section: 0)), + cell + ) + } +} + +final class MockCollectionView: UICollectionView { + var isPerformBatchUpdatesCalledCount = 0 + + init() { + super.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + + let window = UIWindow() + window.addSubview(self) + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func performBatchUpdates(_ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil) { + isPerformBatchUpdatesCalledCount += 1 + updates?() + completion?(true) + } + + override func insertSections(_ sections: IndexSet) {} + override func insertItems(at indexPaths: [IndexPath]) {} +} + +#endif diff --git a/Tests/DiffableDataSourceTests.swift b/Tests/DiffableDataSourceTests.swift new file mode 100644 index 0000000..59614a1 --- /dev/null +++ b/Tests/DiffableDataSourceTests.swift @@ -0,0 +1,895 @@ +import XCTest +@testable import DiffableDataSources + +// swiftlint:disable file_length +// swiftlint:disable type_body_length + +final class DiffableDataSourceSnapshotTests: XCTestCase { + func testAppendSections() { + typealias Test = (initial: [Int], append: [Int], expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [], [0, 1]), + ([], [0, 1, 2], [0, 1, 2]), + ([0, 1], [2, 3, 4], [0, 1, 2, 3, 4]), + ([0, 1], [4, 3, 2], [0, 1, 4, 3, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.appendSections(test.append) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testAppendSectionsDuplication() { + typealias Test = (initial: [Int], append: [Int], expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [0], [0, 1, 0]), + ([0, 1], [0, 1], [0, 1, 0, 1]), + ([0, 1], [2, 2], [0, 1, 2, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.appendSections(test.append) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testInsertSectionsBeforeSection() { + typealias Test = (initial: [Int], insert: [Int], before: Int, expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [3, 4], 1, [0, 3, 4, 1]), + ([0, 1], [3, 4], 0, [3, 4, 0, 1]), + ([0, 1, 2], [3, 4], 2, [0, 1, 3, 4, 2]), + ([0, 1, 2], [], 2, [0, 1, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.insertSections(test.insert, beforeSection: test.before) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testInsertSectionsBeforeSectionDuplication() { + typealias Test = (initial: [Int], insert: [Int], before: Int, expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [1], 0, [1, 0, 1]), + ([0, 1], [2, 2], 1, [0, 2, 2, 1]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.insertSections(test.insert, beforeSection: test.before) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testInsertSectionsAfterSection() { + typealias Test = (initial: [Int], insert: [Int], after: Int, expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [3, 4], 1, [0, 1, 3, 4]), + ([0, 1], [3, 4], 0, [0, 3, 4, 1]), + ([0, 1, 2], [3, 4], 2, [0, 1, 2, 3, 4]), + ([0, 1, 2], [], 2, [0, 1, 2]), + ([0], [1], 0, [0, 1]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.insertSections(test.insert, afterSection: test.after) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testInsertSectionsAfterSectionDuplication() { + typealias Test = (initial: [Int], insert: [Int], after: Int, expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [1], 0, [0, 1, 1]), + ([0, 1], [2, 2], 1, [0, 1, 2, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.insertSections(test.insert, afterSection: test.after) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testDeleteSections() { + typealias Test = (initial: [Int], delete: [Int], expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [1], [0]), + ([0, 1], [0], [1]), + ([0, 1, 2], [1], [0, 2]), + ([0, 1], [1], [0]), + ([], [1], []), + ([0, 1], [100], [0, 1]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.deleteSections(test.delete) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testMoveSectionBeforeSection() { + typealias Test = (initial: [Int], move: Int, before: Int, expected: [Int]) + + let tests: [Test] = [ + ([0, 1], 1, 0, [1, 0]), + ([0, 1, 2], 2, 0, [2, 0, 1]), + ([0, 1, 2], 0, 2, [1, 0, 2]), + ([0, 1, 2], 1, 2, [0, 1, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.moveSection(test.move, beforeSection: test.before) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testMoveSectionAfterSection() { + typealias Test = (initial: [Int], move: Int, after: Int, expected: [Int]) + + let tests: [Test] = [ + ([0, 1], 0, 1, [1, 0]), + ([0, 1, 2], 2, 0, [0, 2, 1]), + ([0, 1, 2], 0, 2, [1, 2, 0]), + ([0, 1, 2], 1, 0, [0, 1, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.moveSection(test.move, afterSection: test.after) + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testReloadSections() { + typealias Section = SnapshotStructure.Section + typealias Test = (initial: [Int], reload: [Int], sections: [Section]) + + let tests: [Test] = [ + ([], [], []), + ([0], [1], [ + Section(id: 0, items: [], isReloaded: false) + ] + ), + ([0], [0], [ + Section(id: 0, items: [], isReloaded: true) + ] + ), + ([0, 1, 2], [2], [ + Section(id: 0, items: [], isReloaded: false), + Section(id: 1, items: [], isReloaded: false), + Section(id: 2, items: [], isReloaded: true) + ] + ), + ([2, 1, 0], [0, 1], [ + Section(id: 2, items: [], isReloaded: false), + Section(id: 1, items: [], isReloaded: true), + Section(id: 0, items: [], isReloaded: true) + ] + ) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + snapshot.reloadSections(test.reload) + XCTAssertEqual(snapshot.sectionIdentifiers, test.initial) + + XCTAssertEqual(snapshot.structure.sections, test.sections) + } + } + + func testAppendItems() { + typealias Test = (initial: [Int], append: [Int], expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [2, 3], [0, 1, 2, 3]), + ([], [2, 3], [2, 3]), + ([1], [0], [1, 0]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1]) + snapshot.appendItems(test.initial) + snapshot.appendItems(test.append) + + XCTAssertEqual(snapshot.itemIdentifiers(inSection: 1), test.expected) + } + } + + func testAppendItemsDuplication() { + typealias Test = (initial: [Int], append: [Int], expected: [Int]) + + let tests: [Test] = [ + ([1], [1, 2], [1, 1, 2]), + ([1], [2, 2], [1, 2, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1]) + snapshot.appendItems(test.initial) + snapshot.appendItems(test.append) + + XCTAssertEqual(snapshot.itemIdentifiers(inSection: 1), test.expected) + } + } + + func testAppendItemsToSection() { + typealias Test = (initial: [[Int]], append: [Int], section: Int, expected: [[Int]]) + + let tests: [Test] = [ + ([[], [0, 1]], [2, 3], 1, [[], [0, 1, 2, 3]]), + ([[], []], [2, 3], 1, [[], [2, 3]]), + ([[], [1]], [0], 1, [[], [1, 0]]), + ([[], [1]], [2], 0, [[2], [1]]), + ([[], [1]], [2, 3], 0, [[2, 3], [1]]), + ([[], []], [0], 0, [[0], []]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items) + } + + snapshot.appendItems(test.append, toSection: test.section) + + for (section, items) in test.expected.enumerated() { + XCTAssertEqual(snapshot.itemIdentifiers(inSection: section), items) + } + } + } + + func testAppendItemsToSectionDuplication() { + typealias Test = (initial: [[Int]], append: [Int], section: Int, expected: [[Int]]) + + let tests: [Test] = [ + ([[], [1]], [1, 2], 1, [[], [1, 1, 2]]), + ([[], [1]], [2, 2], 1, [[], [1, 2, 2]]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items) + } + + snapshot.appendItems(test.append, toSection: test.section) + + for (section, items) in test.expected.enumerated() { + XCTAssertEqual(snapshot.itemIdentifiers(inSection: section), items) + } + } + } + + func testInsertItemsBeforeItem() { + typealias Test = (initial: [Int], insert: [Int], before: Int, expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [2, 3], 1, [0, 2, 3, 1]), + ([0, 1], [2, 3], 0, [2, 3, 0, 1]), + ([0, 1, 2], [3, 4], 1, [0, 3, 4, 1, 2]), + ([0, 1, 2], [], 1, [0, 1, 2]), + ([0], [1], 0, [1, 0]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1]) + snapshot.appendItems(test.initial) + snapshot.insertItems(test.insert, beforeItem: test.before) + + XCTAssertEqual(snapshot.itemIdentifiers(inSection: 1), test.expected) + } + } + + func testInsertItemsBeforeItemDuplication() { + typealias Test = (initial: [Int], insert: [Int], before: Int, expected: [Int]) + + let tests: [Test] = [ + ([1], [1, 2], 1, [1, 2, 1]), + ([1], [2, 2], 1, [2, 2, 1]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1]) + snapshot.appendItems(test.initial) + snapshot.insertItems(test.insert, beforeItem: test.before) + + XCTAssertEqual(snapshot.itemIdentifiers(inSection: 1), test.expected) + } + } + + func testInsertItemsAfterItem() { + typealias Test = (initial: [Int], insert: [Int], after: Int, expected: [Int]) + + let tests: [Test] = [ + ([0, 1], [2, 3], 1, [0, 1, 2, 3]), + ([0, 1], [2, 3], 0, [0, 2, 3, 1]), + ([0, 1, 2], [3, 4], 1, [0, 1, 3, 4, 2]), + ([0, 1, 2], [], 1, [0, 1, 2]), + ([0], [1], 0, [0, 1]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1]) + snapshot.appendItems(test.initial) + snapshot.insertItems(test.insert, afterItem: test.after) + + XCTAssertEqual(snapshot.itemIdentifiers(inSection: 1), test.expected) + } + } + + func testInsertItemsAfterItemDuplication() { + typealias Test = (initial: [Int], insert: [Int], after: Int, expected: [Int]) + + let tests: [Test] = [ + ([1], [1, 2], 1, [1, 1, 2]), + ([1], [2, 2], 1, [1, 2, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1]) + snapshot.appendItems(test.initial) + snapshot.insertItems(test.insert, afterItem: test.after) + + XCTAssertEqual(snapshot.itemIdentifiers(inSection: 1), test.expected) + } + } + + func testDeleteItems() { + typealias Test = (initial: [[Int]], delete: [Int], expected: [[Int]]) + + let tests: [Test] = [ + ([[], [0, 1]], [0], [[], [1]]), + ([[0, 1], [2, 3]], [0, 2], [[1], [3]]), + ([[], []], [100], [[], []]), + ([[0, 1], [2, 3]], [0, 1], [[], [2, 3]]), + ([[0, 1], [2, 3]], [0], [[1], [2, 3]]), + ([[0, 1], [2, 3]], [0, 1, 2, 3], [[], []]), + ([[0, 1], [2, 3]], [0, 1, 2, 3, 4, 5], [[], []]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + snapshot.deleteItems(test.delete) + + for (section, items) in test.expected.enumerated() { + XCTAssertEqual(snapshot.itemIdentifiers(inSection: section), items) + } + } + } + + func testDeleteAllItems() { + typealias Test = [[Int]] + + let tests: [Test] = [ + ([[], [0, 1]]), + ([[0], [1]]), + ([[], []]), + ([[0, 1], [2, 3]]), + ([[0, 1, 2], []]), + ([[], [0, 1, 2]]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + snapshot.deleteAllItems() + + XCTAssertEqual(snapshot.itemIdentifiers, []) + } + } + + func testMoveItemsBeforeItem() { + typealias Test = (initial: [[Int]], move: Int, before: Int, expected: [[Int]]) + + let tests: [Test] = [ + ([[0, 1], [2, 3]], 0, 2, [[1], [0, 2, 3]]), + ([[0, 1], [2, 3]], 1, 0, [[1, 0], [2, 3]]), + ([[0, 1], [2, 3]], 3, 0, [[3, 0, 1], [2]]), + ([[0, 1], [2, 3]], 2, 3, [[0, 1], [2, 3]]), + ([[0], [1]], 0, 1, [[], [0, 1]]), + ([[0], [1]], 1, 0, [[1, 0], []]), + ([[], [0, 1]], 1, 0, [[], [1, 0]]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + snapshot.moveItem(test.move, beforeItem: test.before) + + for (section, items) in test.expected.enumerated() { + XCTAssertEqual(snapshot.itemIdentifiers(inSection: section), items) + } + } + } + + func testMoveItemsAfterItem() { + typealias Test = (initial: [[Int]], move: Int, after: Int, expected: [[Int]]) + + let tests: [Test] = [ + ([[0, 1], [2, 3]], 0, 2, [[1], [2, 0, 3]]), + ([[0, 1], [2, 3]], 1, 0, [[0, 1], [2, 3]]), + ([[0, 1], [2, 3]], 3, 0, [[0, 3, 1], [2]]), + ([[0, 1], [2, 3]], 2, 3, [[0, 1], [3, 2]]), + ([[0], [1]], 0, 1, [[], [1, 0]]), + ([[0], [1]], 1, 0, [[0, 1], []]), + ([[], [0, 1]], 1, 0, [[], [0, 1]]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + snapshot.moveItem(test.move, afterItem: test.after) + + for (section, items) in test.expected.enumerated() { + XCTAssertEqual(snapshot.itemIdentifiers(inSection: section), items) + } + } + } + + func testReloadItems() { + typealias Section = SnapshotStructure.Section + typealias Item = SnapshotStructure.Item + typealias Test = (initial: [[Int]], reload: [Int], sections: [Section]) + + let tests: [Test] = [ + ([[], []], [], [ + Section(id: 0, items: [], isReloaded: false), + Section(id: 1, items: [], isReloaded: false) + ] + ), + ([[], [0]], [0], [ + Section(id: 0, items: [], isReloaded: false), + Section( + id: 1, + items: [ + Item(id: 0, isReloaded: true) + ], + isReloaded: false + ) + ] + ), + ([[0, 1], [2]], [1, 2], [ + Section( + id: 0, + items: [ + Item(id: 0, isReloaded: false), + Item(id: 1, isReloaded: true) + ], + isReloaded: false + ), + Section( + id: 1, + items: [ + Item(id: 2, isReloaded: true) + ], + isReloaded: false + ) + ] + ), + ([[0, 1], [2, 3]], [0, 2, 3], [ + Section( + id: 0, + items: [ + Item(id: 0, isReloaded: true), + Item(id: 1, isReloaded: false) + ], + isReloaded: false + ), + Section( + id: 1, + items: [ + Item(id: 2, isReloaded: true), + Item(id: 3, isReloaded: true) + ], + isReloaded: false + ) + ] + ) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + snapshot.reloadItems(test.reload) + + XCTAssertEqual(snapshot.itemIdentifiers, test.initial.flatMap { $0 }) + + XCTAssertEqual(snapshot.structure.sections, test.sections) + } + } + + func testNumberOfItems() { + typealias Test = (initial: [[Int]], expected: Int) + + let tests: [Test] = [ + ([[0, 1], [2, 3]], 4), + ([[0, 1], []], 2), + ([[], [2, 3]], 2), + ([[], []], 0) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.numberOfItems, test.expected) + } + } + + func testNumberOfItemsDuplication() { + typealias Test = (initial: [[Int]], expected: Int) + + let tests: [Test] = [ + ([[0, 1], [1, 2]], 4), + ([[0, 0], []], 2) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.numberOfItems, test.expected) + } + } + + func testNumberOfSections() { + typealias Test = (initial: [Int], expected: Int) + + let tests: [Test] = [ + ([], 0), + ([0, 1, 2], 3) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + + XCTAssertEqual(snapshot.numberOfSections, test.expected) + } + } + + func testNumberOfSectionsDuplication() { + typealias Test = (initial: [Int], expected: Int) + + let tests: [Test] = [ + ([0, 0], 2), + ([0, 1, 1], 3) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + + XCTAssertEqual(snapshot.numberOfSections, test.expected) + } + } + + func testItemIdentifiers() { + typealias Test = (initial: [[Int]], expected: [Int]) + + let tests: [Test] = [ + ([[0, 1], [2, 3]], [0, 1, 2, 3]), + ([[0, 1], []], [0, 1]), + ([[], [2, 3]], [2, 3]), + ([[], []], []) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.itemIdentifiers, test.expected) + } + } + + func testItemIdentifiersDuplication() { + typealias Test = (initial: [[Int]], expected: [Int]) + + let tests: [Test] = [ + ([[0, 1], [1, 2]], [0, 1, 1, 2]), + ([[0, 0], [1, 2]], [0, 0, 1, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.itemIdentifiers, test.expected) + } + } + + func testSectionIdentifiers() { + typealias Test = (initial: [Int], expected: [Int]) + + let tests: [Test] = [ + ([], []), + ([0, 1], [0, 1]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testSectionIdentifiersDuplication() { + typealias Test = (initial: [Int], expected: [Int]) + + let tests: [Test] = [ + ([0, 0], [0, 0]), + ([0, 1, 1], [0, 1, 1]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + + XCTAssertEqual(snapshot.sectionIdentifiers, test.expected) + } + } + + func testNumberOfItemsInSection() { + typealias Test = (initial: [[Int]], expectedInRight: Int) + + let tests: [Test] = [ + ([[0, 1], [2, 3]], 2), + ([[0, 1], []], 0), + ([[], [2, 3]], 2), + ([[], []], 0) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.numberOfItems(inSection: 1), test.expectedInRight) + } + } + + func testNumberOfItemsInSectionDuplication() { + typealias Test = (initial: [[Int]], expectedInRight: Int) + + let tests: [Test] = [ + ([[0], [1, 1]], 2), + ([[0, 1], [1, 2]], 2) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.numberOfItems(inSection: 1), test.expectedInRight) + } + } + + func testItemIdentifiersInSection() { + typealias Test = (initial: [[Int]], expectedInRight: [Int]) + + let tests: [Test] = [ + ([[0, 1], [2, 3]], [2, 3]), + ([[0, 1], []], []), + ([[], [2, 3]], [2, 3]), + ([[], []], []) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.itemIdentifiers(inSection: 1), test.expectedInRight) + } + } + + func testItemIdentifiersInSectionDuplication() { + typealias Test = (initial: [[Int]], expectedInRight: [Int]) + + let tests: [Test] = [ + ([[0, 1], [1, 2]], [1, 2]), + ([[0, 1], [2, 2]], [2, 2]) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.itemIdentifiers(inSection: 1), test.expectedInRight) + } + } + + func testSectionIdentifiersContainingItem() { + typealias Test = (initial: [[Int]], item: Int, expected: Int?) + + let tests: [Test] = [ + ([[0, 1], [2, 3]], 2, 1), + ([[0, 1], []], 0, 0), + ([[], [2, 3]], 2, 1), + ([[], []], 0, nil), + ([[0], [1]], 2, nil) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.sectionIdentifier(containingItem: test.item), test.expected) + } + } + + func testSectionIdentifiersContainingDulication() { + typealias Test = (initial: [[Int]], item: Int, expected: Int?) + + let tests: [Test] = [ + ([[0, 1], [1, 2]], 2, 1), + ([[0, 1], [1, 2]], 1, 1) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.sectionIdentifier(containingItem: test.item), test.expected) + } + } + + func testIndexOfItem() { + typealias Test = (initial: [[Int]], item: Int, expectedIndex: Int?) + + let tests: [Test] = [ + ([[0, 1], [2, 3]], 2, 2), + ([[0, 1], []], 0, 0), + ([[], [2, 3]], 2, 0), + ([[], []], 0, nil), + ([[0], [1]], 2, nil) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.indexOfItem(test.item), test.expectedIndex) + } + } + + func testIndexOfItemDuplication() { + typealias Test = (initial: [[Int]], item: Int, expectedIndex: Int?) + + let tests: [Test] = [ + ([[0, 1], [1, 2]], 2, 3), + ([[0, 1], [1, 2]], 1, 1) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + for (section, items) in test.initial.enumerated() { + snapshot.appendSections([section]) + snapshot.appendItems(items, toSection: section) + } + + XCTAssertEqual(snapshot.indexOfItem(test.item), test.expectedIndex) + } + } + + func testIndexOfSection() { + typealias Test = (initial: [Int], section: Int, expectedIndex: Int?) + + let tests: [Test] = [ + ([0, 1, 2], 1, 1), + ([0, 1, 2], 2, 2), + ([0, 1, 2], 3, nil), + ([], 0, nil) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + + XCTAssertEqual(snapshot.indexOfSection(test.section), test.expectedIndex) + } + } + + func testIndexOfSectionDuplication() { + typealias Test = (initial: [Int], section: Int, expectedIndex: Int?) + + let tests: [Test] = [ + ([0, 1, 1, 2], 1, 1), + ([0, 1, 1, 2], 2, 3) + ] + + for test in tests { + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections(test.initial) + + XCTAssertEqual(snapshot.indexOfSection(test.section), test.expectedIndex) + } + } +} diff --git a/Tests/Info.plist b/Tests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Tests/MainThreadSerialDispatcherTests.swift b/Tests/MainThreadSerialDispatcherTests.swift new file mode 100644 index 0000000..add63bb --- /dev/null +++ b/Tests/MainThreadSerialDispatcherTests.swift @@ -0,0 +1,58 @@ +import XCTest +@testable import DiffableDataSources + +final class MainThreadSerialDispatcherTests: XCTestCase { + func testMainThread() { + let dispatcher = MainThreadSerialDispatcher() + let queue = DispatchQueue.global() + let expectation = self.expectation(description: "testMainThread") + + queue.async { + dispatcher.dispatch { + XCTAssertTrue(Thread.isMainThread) + expectation.fulfill() + } + } + + waitForExpectations(timeout: 1) + } + + func testMainThreadSerial() { + let dispatcher = MainThreadSerialDispatcher() + let queue = DispatchQueue.global() + let expectation = self.expectation(description: "testMainThreadSerial") + + var array = [Int]() + + let group = DispatchGroup() + + queue.async(group: group) { + dispatcher.dispatch { + array.append(0) + } + } + + group.wait() + + queue.async(group: group) { + dispatcher.dispatch { + array.append(1) + } + + dispatcher.dispatch { + array.append(2) + } + } + + group.wait() + + dispatcher.dispatch { + array.append(3) + expectation.fulfill() + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(array, [0, 1, 2, 3]) + } + } +} diff --git a/Tests/TableViewDiffableDataSourceTests.swift b/Tests/TableViewDiffableDataSourceTests.swift new file mode 100644 index 0000000..31caa27 --- /dev/null +++ b/Tests/TableViewDiffableDataSourceTests.swift @@ -0,0 +1,186 @@ +#if os(iOS) || os(tvOS) + +import XCTest +import UIKit +@testable import DiffableDataSources + +final class TableViewDiffableDataSourceTests: XCTestCase { + func testInit() { + let tableView = MockTableView() + let dataSource = TableViewDiffableDataSource(tableView: tableView) { _, _, _ in + UITableViewCell() + } + + XCTAssertTrue(tableView.dataSource === dataSource) + } + + func testApply() { + let tableView = MockTableView() + let dataSource = TableViewDiffableDataSource(tableView: tableView) { _, _, _ in + UITableViewCell() + } + + let snapshot = DiffableDataSourceSnapshot() + + dataSource.apply(snapshot) + XCTAssertEqual(tableView.isPerformBatchUpdatesCalledCount, 0) + + snapshot.appendSections([0]) + snapshot.appendItems([0]) + + dataSource.apply(snapshot) + XCTAssertEqual(tableView.isPerformBatchUpdatesCalledCount, 1) + + dataSource.apply(snapshot) + XCTAssertEqual(tableView.isPerformBatchUpdatesCalledCount, 1) + + snapshot.appendItems([1]) + + dataSource.apply(snapshot) + XCTAssertEqual(tableView.isPerformBatchUpdatesCalledCount, 2) + } + + func testSnapshot() { + let tableView = MockTableView() + let dataSource = TableViewDiffableDataSource(tableView: tableView) { _, _, _ in + UITableViewCell() + } + + let snapshot1 = dataSource.snapshot() + XCTAssertEqual(snapshot1.sectionIdentifiers, []) + XCTAssertEqual(snapshot1.itemIdentifiers, []) + + dataSource.snapshot().appendSections([0, 1, 2]) + let snapshot2 = dataSource.snapshot() + XCTAssertEqual(snapshot2.sectionIdentifiers, []) + XCTAssertEqual(snapshot2.itemIdentifiers, []) + + let snapshotToApply = DiffableDataSourceSnapshot() + snapshotToApply.appendSections([0, 1, 2]) + snapshotToApply.appendItems([0, 1, 2]) + dataSource.apply(snapshotToApply) + + let snapshot3 = dataSource.snapshot() + XCTAssertEqual(snapshot3.sectionIdentifiers, [0, 1, 2]) + XCTAssertEqual(snapshot3.itemIdentifiers, [0, 1, 2]) + + dataSource.snapshot().appendSections([3, 4, 5]) + + let snapshot4 = dataSource.snapshot() + XCTAssertEqual(snapshot4.sectionIdentifiers, [0, 1, 2]) + XCTAssertEqual(snapshot4.itemIdentifiers, [0, 1, 2]) + + snapshot4.appendSections([3, 4, 5]) + snapshot4.appendItems([3, 4, 5]) + dataSource.apply(snapshot4) + + let snapshot5 = dataSource.snapshot() + XCTAssertEqual(snapshot5.sectionIdentifiers, [0, 1, 2, 3, 4, 5]) + XCTAssertEqual(snapshot5.itemIdentifiers, [0, 1, 2, 3, 4, 5]) + } + + func testItemIdentifier() { + let tableView = MockTableView() + let dataSource = TableViewDiffableDataSource(tableView: tableView) { _, _, _ in + UITableViewCell() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.itemIdentifier(for: IndexPath(item: 1, section: 0)), 1) + XCTAssertEqual(dataSource.itemIdentifier(for: IndexPath(item: 100, section: 100)), nil) + } + + func testIndexPath() { + let tableView = MockTableView() + let dataSource = TableViewDiffableDataSource(tableView: tableView) { _, _, _ in + UITableViewCell() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.indexPath(for: 2), IndexPath(item: 2, section: 0)) + XCTAssertEqual(dataSource.indexPath(for: 100), nil) + } + + func testNumberOfSections() { + let tableView = MockTableView() + let dataSource = TableViewDiffableDataSource(tableView: tableView) { _, _, _ in + UITableViewCell() + } + + XCTAssertEqual(dataSource.numberOfSections(in: tableView), 0) + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.numberOfSections(in: tableView), 3) + } + + func testNumberOfRowsInSection() { + let tableView = MockTableView() + let dataSource = TableViewDiffableDataSource(tableView: tableView) { _, _, _ in + UITableViewCell() + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection: 0), 3) + } + + func testCellForRowAt() { + let tableView = MockTableView() + let cell = UITableViewCell() + let dataSource = TableViewDiffableDataSource(tableView: tableView) { _, _, _ in + cell + } + + let snapshot = DiffableDataSourceSnapshot() + snapshot.appendSections([0, 1, 2]) + snapshot.appendItems([0, 1, 2], toSection: 0) + dataSource.apply(snapshot) + + XCTAssertEqual( + dataSource.tableView(tableView, cellForRowAt: IndexPath(item: 1, section: 0)), + cell + ) + } +} + +final class MockTableView: UITableView { + var isPerformBatchUpdatesCalledCount = 0 + + init() { + super.init(frame: .zero, style: .plain) + + let window = UIWindow() + window.addSubview(self) + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func performBatchUpdates(_ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil) { + isPerformBatchUpdatesCalledCount += 1 + updates?() + completion?(true) + } + + override func insertSections(_ sections: IndexSet, with animation: RowAnimation) {} + override func insertRows(at indexPaths: [IndexPath], with animation: RowAnimation) {} +} + +#endif diff --git a/XCConfigs/DiffableDataSources.xcconfig b/XCConfigs/DiffableDataSources.xcconfig new file mode 100644 index 0000000..1ede572 --- /dev/null +++ b/XCConfigs/DiffableDataSources.xcconfig @@ -0,0 +1,22 @@ +MACOSX_DEPLOYMENT_TARGET = 10.11 +IPHONEOS_DEPLOYMENT_TARGET = 9.0 +TVOS_DEPLOYMENT_TARGET = 9.0 + +SDKROOT = +SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator appletvos appletvsimulator +TARGETED_DEVICE_FAMILY = 1,2,3 +VALID_ARCHS[sdk=macosx*] = i386 x86_64 +VALID_ARCHS[sdk=iphoneos*] = arm64 armv7 armv7s +VALID_ARCHS[sdk=iphonesimulator*] = i386 x86_64 +VALID_ARCHS[sdk=appletv*] = arm64 +VALID_ARCHS[sdk=appletvsimulator*] = x86_64 + +CODE_SIGN_IDENTITY = +CODE_SIGN_STYLE = Manual +INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks +SKIP_INSTALL = YES +DYLIB_COMPATIBILITY_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +DYLIB_INSTALL_NAME_BASE = @rpath +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks @loader_path/../Frameworks +DEFINES_MODULE = NO diff --git a/assets/insertion_sort.gif b/assets/insertion_sort.gif new file mode 100644 index 0000000..31d84f1 Binary files /dev/null and b/assets/insertion_sort.gif differ diff --git a/assets/mountains.gif b/assets/mountains.gif new file mode 100644 index 0000000..083d147 Binary files /dev/null and b/assets/mountains.gif differ diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..7575596 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,41 @@ +trigger: +- master + +jobs: +- job: macOS + pool: + vmImage: 'macOS 10.14' + strategy: + maxParallel: 10 + matrix: + xcode10_2: + DEVELOPER_DIR: /Applications/Xcode_10.2.app + steps: + - checkout: self + submodules: true + - script: xcodebuild -version + displayName: xcodebuild -version + condition: succeededOrFailed() + - script: | + set -o pipefail && + xcodebuild build-for-testing test-without-building -workspace DiffableDataSources.xcworkspace -scheme DiffableDataSources -configuration Release ENABLE_TESTABILITY=YES | + xcpretty -c -r junit -o build/reports/xcodebuild-macOS.xml + displayName: xcodebuild test maxOS + condition: succeededOrFailed() + - script: | + set -o pipefail && + xcodebuild build-for-testing test-without-building -workspace DiffableDataSources.xcworkspace -scheme DiffableDataSources -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone Xs' ENABLE_TESTABILITY=YES | + xcpretty -c -r junit -o build/reports/xcodebuild-iOS.xml + displayName: xcodebuild test iOS + condition: succeededOrFailed() + - script: | + set -o pipefail && + xcodebuild build-for-testing test-without-building -workspace DiffableDataSources.xcworkspace -scheme DiffableDataSources -configuration Release -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' ENABLE_TESTABILITY=YES | + xcpretty -c -r junit -o build/reports/xcodebuild-tvOS.xml + displayName: xcodebuild test tvOS + condition: succeededOrFailed() + - task: PublishTestResults@2 + inputs: + testRunner: JUnit + testResultsFiles: build/reports/** + condition: succeededOrFailed() diff --git a/docs/Classes/CollectionViewDiffableDataSource.html b/docs/Classes/CollectionViewDiffableDataSource.html new file mode 100644 index 0000000..a1c33d6 --- /dev/null +++ b/docs/Classes/CollectionViewDiffableDataSource.html @@ -0,0 +1,686 @@ + + + + CollectionViewDiffableDataSource Class Reference + + + + + + + + + + + + + + + + +
+

+ + DiffableDataSources Docs + + (100% documented) +

+ +

+

+ +
+

+ +

+ + + View on GitHub + +

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

CollectionViewDiffableDataSource

+
+
+
open class CollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject, UICollectionViewDataSource where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
+ +
+
+

A class for backporting UICollectionViewDiffableDataSource introduced in iOS 13.0+, tvOS 13.0+. +Represents the data model object for UICollectionView that can be applies the +changes with automatic diffing.

+ +
+
+ +
+
+
+
    +
  • +
    + + + + CellProvider + +
    +
    +
    +
    +
    +
    +

    The type of closure providing the cell.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias CellProvider = (UICollectionView, IndexPath, ItemIdentifierType) -> UICollectionViewCell?
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    The type of closure providing the supplementary view for element of kind.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias SupplementaryViewProvider = (UICollectionView, String, IndexPath) -> UICollectionReusableView?
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    A closure to dequeue the views for element of kind.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public var supplementaryViewProvider: CollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.SupplementaryViewProvider?
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Creates a new data source.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public init(collectionView: UICollectionView, cellProvider: @escaping CellProvider)
    + +
    +
    +
    +

    Parameters

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

    A collection view instance to be managed.

    +
    +
    + + cellProvider + + +
    +

    A closure to dequeue the cell for items.

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

    Applies given snapshot to perform automatic diffing update.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func apply(_ snapshot: DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>, animatingDifferences: Bool = true)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + snapshot + + +
    +

    A snapshot object to be applied to data model.

    +
    +
    + + animatingDifferences + + +
    +

    A Boolean value indicating whether to update with + diffing animation.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + snapshot() + +
    +
    +
    +
    +
    +
    +

    Returns a new snapshot object of current state.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func snapshot() -> DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>
    + +
    +
    +
    +

    Return Value

    +

    A new snapshot object of current state.

    +
    +
    +
    +
  • +
  • +
    + + + + itemIdentifier(for:) + +
    +
    +
    +
    +
    +
    +

    Returns an item identifier for given index path.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType?
    + +
    +
    +
    +

    Parameters

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

    An index path for the item identifier.

    +
    +
    +
    +
    +

    Return Value

    +

    An item identifier for given index path.

    +
    +
    +
    +
  • +
  • +
    + + + + indexPath(for:) + +
    +
    +
    +
    +
    +
    +

    Returns an index path for given item identifier.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath?
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + itemIdentifier + + +
    +

    An identifier of item.

    +
    +
    +
    +
    +

    Return Value

    +

    An index path for given item identifier.

    +
    +
    +
    +
  • +
  • +
    + + + + numberOfSections(in:) + +
    +
    +
    +
    +
    +
    +

    Returns the number of sections in the data source.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func numberOfSections(in collectionView: UICollectionView) -> Int
    + +
    +
    +
    +

    Parameters

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

    A collection view instance managed by self.

    +
    +
    +
    +
    +

    Return Value

    +

    The number of sections in the data source.

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

    Returns the number of items in the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    + +
    +
    +
    +

    Parameters

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

    A collection view instance managed by self.

    +
    +
    + + section + + +
    +

    An index of section.

    +
    +
    +
    +
    +

    Return Value

    +

    The number of items in the specified section.

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

    Returns a cell for item at specified index path.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
    + +
    +
    +
    +

    Parameters

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

    A collection view instance managed by self.

    +
    +
    + + indexPath + + +
    +

    An index path for cell.

    +
    +
    +
    +
    +

    Return Value

    +

    A cell for row at specified index path.

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

    Returns a supplementary view for element of kind at specified index path.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
    + +
    +
    +
    +

    Parameters

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

    A collection view instance managed by self.

    +
    +
    + + kind + + +
    +

    The kind of element to be display.

    +
    +
    + + indexPath + + +
    +

    An index path for supplementary view.

    +
    +
    +
    +
    +

    Return Value

    +

    A supplementary view for element of kind at specified index path.

    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + + diff --git a/docs/Classes/DiffableDataSourceSnapshot.html b/docs/Classes/DiffableDataSourceSnapshot.html new file mode 100644 index 0000000..6a726ec --- /dev/null +++ b/docs/Classes/DiffableDataSourceSnapshot.html @@ -0,0 +1,1272 @@ + + + + DiffableDataSourceSnapshot Class Reference + + + + + + + + + + + + + + + + +
+

+ + DiffableDataSources Docs + + (100% documented) +

+ +

+

+ +
+

+ +

+ + + View on GitHub + +

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

DiffableDataSourceSnapshot

+
+
+
public class DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
+ +
+
+

A class for backporting NSDiffableDataSourceSnapshot introduced in iOS 13.0+, macOS 10.15+, tvOS 13.0+. +Represents the mutable state of diffable data source of UI.

+ +
+
+ +
+
+
+
    +
  • +
    + + + + init() + +
    +
    +
    +
    +
    +
    +

    Creates a new empty snapshot object.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public init()
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + numberOfItems + +
    +
    +
    +
    +
    +
    +

    The number of item identifiers in the snapshot.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public var numberOfItems: Int { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + numberOfSections + +
    +
    +
    +
    +
    +
    +

    The number of section identifiers in the snapshot.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public var numberOfSections: Int { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + sectionIdentifiers + +
    +
    +
    +
    +
    +
    +

    All section identifiers in the snapshot.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public var sectionIdentifiers: [SectionIdentifierType] { get }
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + itemIdentifiers + +
    +
    +
    +
    +
    +
    +

    All item identifiers in the snapshot.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public var itemIdentifiers: [ItemIdentifierType] { get }
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Returns the number of item identifiers in the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func numberOfItems(inSection identifier: SectionIdentifierType) -> Int
    + +
    +
    +
    +

    Parameters

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

    An identifier of section.

    +
    +
    +
    +
    +

    Return Value

    +

    The number of item identifiers in the specified section.

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

    Returns the item identifiers in the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func itemIdentifiers(inSection identifier: SectionIdentifierType) -> [ItemIdentifierType]
    + +
    +
    +
    +

    Parameters

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

    An identifier of section.

    +
    +
    +
    +
    +

    Return Value

    +

    The item identifiers in the specified section.

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

    Returns a section identifier containing the specified item.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func sectionIdentifier(containingItem identifier: ItemIdentifierType) -> SectionIdentifierType?
    + +
    +
    +
    +

    Parameters

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

    An identifier of item.

    +
    +
    +
    +
    +

    Return Value

    +

    A section identifier containing the specified item.

    +
    +
    +
    +
  • +
  • +
    + + + + indexOfItem(_:) + +
    +
    +
    +
    +
    +
    +

    Returns an index of the specified item.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func indexOfItem(_ identifier: ItemIdentifierType) -> Int?
    + +
    +
    +
    +

    Parameters

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

    An identifier of item.

    +
    +
    +
    +
    +

    Return Value

    +

    An index of the specified item.

    +
    +
    +
    +
  • +
  • +
    + + + + indexOfSection(_:) + +
    +
    +
    +
    +
    +
    +

    Returns an index of the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func indexOfSection(_ identifier: SectionIdentifierType) -> Int?
    + +
    +
    +
    +

    Parameters

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

    An identifier of section.

    +
    +
    +
    +
    +

    Return Value

    +

    An index of the specified section.

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

    Appends the given item identifiers to the specified section or last section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func appendItems(_ identifiers: [ItemIdentifierType], toSection sectionIdentifier: SectionIdentifierType? = nil)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + identifiers + + +
    +

    The item identifiers to be appended.

    +
    +
    + + sectionIdentifier + + +
    +

    An identifier of section to append the given identiciers.

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

    Inserts the given item identifiers before the specified item.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func insertItems(_ identifiers: [ItemIdentifierType], beforeItem beforeIdentifier: ItemIdentifierType)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + identifiers + + +
    +

    The item identifiers to be inserted.

    +
    +
    + + beforeIdentifier + + +
    +

    An identifier of item.

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

    Inserts the given item identifiers after the specified item.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func insertItems(_ identifiers: [ItemIdentifierType], afterItem afterIdentifier: ItemIdentifierType)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + identifiers + + +
    +

    The item identifiers to be inserted.

    +
    +
    + + afterIdentifier + + +
    +

    An identifier of item.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + deleteItems(_:) + +
    +
    +
    +
    +
    +
    +

    Deletes the specified items.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func deleteItems(_ identifiers: [ItemIdentifierType])
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifiers + + +
    +

    The item identifiers to be deleted.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + deleteAllItems() + +
    +
    +
    +
    +
    +
    +

    Deletes the all items in the snapshot.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func deleteAllItems()
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Moves the given item identifier before the specified item.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func moveItem(_ identifier: ItemIdentifierType, beforeItem toIdentifier: ItemIdentifierType)
    + +
    +
    +
    +

    Parameters

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

    An item identifier to be moved.

    +
    +
    + + toIdentifier + + +
    +

    An identifier of item.

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

    Moves the given item identifier after the specified item.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func moveItem(_ identifier: ItemIdentifierType, afterItem toIdentifier: ItemIdentifierType)
    + +
    +
    +
    +

    Parameters

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

    An item identifier to be moved.

    +
    +
    + + toIdentifier + + +
    +

    An identifier of item.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + reloadItems(_:) + +
    +
    +
    +
    +
    +
    +

    Reloads the specified items.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func reloadItems(_ identifiers: [ItemIdentifierType])
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifiers + + +
    +

    The item identifiers to be reloaded.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + appendSections(_:) + +
    +
    +
    +
    +
    +
    +

    Appends the given section identifiers to the end of sections.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func appendSections(_ identifiers: [SectionIdentifierType])
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifiers + + +
    +

    The section identifiers to be appended.

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

    Inserts the given section identifiers before the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func insertSections(_ identifiers: [SectionIdentifierType], beforeSection toIdentifier: SectionIdentifierType)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + identifiers + + +
    +

    The section identifiers to be inserted.

    +
    +
    + + toIdentifier + + +
    +

    An identifier of setion.

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

    Inserts the given section identifiers after the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func insertSections(_ identifiers: [SectionIdentifierType], afterSection toIdentifier: SectionIdentifierType)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + identifiers + + +
    +

    The section identifiers to be inserted.

    +
    +
    + + toIdentifier + + +
    +

    An identifier of setion.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + deleteSections(_:) + +
    +
    +
    +
    +
    +
    +

    Deletes the specified sections.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func deleteSections(_ identifiers: [SectionIdentifierType])
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifiers + + +
    +

    The section identifiers to be deleted.

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

    Moves the given section identifier before the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func moveSection(_ identifier: SectionIdentifierType, beforeSection toIdentifier: SectionIdentifierType)
    + +
    +
    +
    +

    Parameters

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

    A section identifier to be moved.

    +
    +
    + + toIdentifier + + +
    +

    An identifier of section.

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

    Moves the given section identifier after the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func moveSection(_ identifier: SectionIdentifierType, afterSection toIdentifier: SectionIdentifierType)
    + +
    +
    +
    +

    Parameters

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

    A section identifier to be moved.

    +
    +
    + + toIdentifier + + +
    +

    An identifier of section.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + reloadSections(_:) + +
    +
    +
    +
    +
    +
    +

    Reloads the specified sections.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func reloadSections(_ identifiers: [SectionIdentifierType])
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + identifiers + + +
    +

    The section identifiers to be reloaded.

    +
    +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + + diff --git a/docs/Classes/TableViewDiffableDataSource.html b/docs/Classes/TableViewDiffableDataSource.html new file mode 100644 index 0000000..61e54bd --- /dev/null +++ b/docs/Classes/TableViewDiffableDataSource.html @@ -0,0 +1,585 @@ + + + + TableViewDiffableDataSource Class Reference + + + + + + + + + + + + + + + + +
+

+ + DiffableDataSources Docs + + (100% documented) +

+ +

+

+ +
+

+ +

+ + + View on GitHub + +

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

TableViewDiffableDataSource

+
+
+
open class TableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject, UITableViewDataSource where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
+ +
+
+

A class for backporting UITableViewDiffableDataSource introduced in iOS 13.0+, tvOS 13.0+. +Represents the data model object for UITableView that can be applies the +changes with automatic diffing.

+ +
+
+ +
+
+
+
    +
  • +
    + + + + CellProvider + +
    +
    +
    +
    +
    +
    +

    The type of closure providing the cell.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias CellProvider = (UITableView, IndexPath, ItemIdentifierType) -> UITableViewCell?
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + defaultRowAnimation + +
    +
    +
    +
    +
    +
    +

    The default animation to updating the views.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public var defaultRowAnimation: UITableView.RowAnimation
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Creates a new data source.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public init(tableView: UITableView, cellProvider: @escaping CellProvider)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + tableView + + +
    +

    A table view instance to be managed.

    +
    +
    + + cellProvider + + +
    +

    A closure to dequeue the cell for rows.

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

    Applies given snapshot to perform automatic diffing update.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func apply(_ snapshot: DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>, animatingDifferences: Bool = true)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + snapshot + + +
    +

    A snapshot object to be applied to data model.

    +
    +
    + + animatingDifferences + + +
    +

    A Boolean value indicating whether to update with + diffing animation.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + snapshot() + +
    +
    +
    +
    +
    +
    +

    Returns a new snapshot object of current state.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func snapshot() -> DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>
    + +
    +
    +
    +

    Return Value

    +

    A new snapshot object of current state.

    +
    +
    +
    +
  • +
  • +
    + + + + itemIdentifier(for:) + +
    +
    +
    +
    +
    +
    +

    Returns an item identifier for given index path.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType?
    + +
    +
    +
    +

    Parameters

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

    An index path for the item identifier.

    +
    +
    +
    +
    +

    Return Value

    +

    An item identifier for given index path.

    +
    +
    +
    +
  • +
  • +
    + + + + indexPath(for:) + +
    +
    +
    +
    +
    +
    +

    Returns an index path for given item identifier.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath?
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + itemIdentifier + + +
    +

    An identifier of item.

    +
    +
    +
    +
    +

    Return Value

    +

    An index path for given item identifier.

    +
    +
    +
    +
  • +
  • +
    + + + + numberOfSections(in:) + +
    +
    +
    +
    +
    +
    +

    Returns the number of sections in the data source.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func numberOfSections(in tableView: UITableView) -> Int
    + +
    +
    +
    +

    Parameters

    + + + + + + + +
    + + tableView + + +
    +

    A table view instance managed by self.

    +
    +
    +
    +
    +

    Return Value

    +

    The number of sections in the data source.

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

    Returns the number of items in the specified section.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + tableView + + +
    +

    A table view instance managed by self.

    +
    +
    + + section + + +
    +

    An index of section.

    +
    +
    +
    +
    +

    Return Value

    +

    The number of items in the specified section.

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

    Returns a cell for row at specified index path.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + tableView + + +
    +

    A table view instance managed by self.

    +
    +
    + + indexPath + + +
    +

    An index path for cell.

    +
    +
    +
    +
    +

    Return Value

    +

    A cell for row at specified index path.

    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + + diff --git a/docs/DataSources.html b/docs/DataSources.html new file mode 100644 index 0000000..1ac2556 --- /dev/null +++ b/docs/DataSources.html @@ -0,0 +1,164 @@ + + + + DataSources Reference + + + + + + + + + + + + + + + + +
+

+ + DiffableDataSources Docs + + (100% documented) +

+ +

+

+ +
+

+ +

+ + + View on GitHub + +

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

DataSources

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

    A class for backporting UITableViewDiffableDataSource introduced in iOS 13.0+, tvOS 13.0+. +Represents the data model object for UITableView that can be applies the +changes with automatic diffing.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open class TableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject, UITableViewDataSource where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
    + +
    +
    +
    +
    +
  • +
+
+
+
    +
  • + +
    +
    +
    +
    +
    +

    A class for backporting UICollectionViewDiffableDataSource introduced in iOS 13.0+, tvOS 13.0+. +Represents the data model object for UICollectionView that can be applies the +changes with automatic diffing.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open class CollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject, UICollectionViewDataSource where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + + diff --git a/docs/Snapshot.html b/docs/Snapshot.html new file mode 100644 index 0000000..2fa5584 --- /dev/null +++ b/docs/Snapshot.html @@ -0,0 +1,129 @@ + + + + Snapshot Reference + + + + + + + + + + + + + + + + +
+

+ + DiffableDataSources Docs + + (100% documented) +

+ +

+

+ +
+

+ +

+ + + View on GitHub + +

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

Snapshot

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

    A class for backporting NSDiffableDataSourceSnapshot introduced in iOS 13.0+, macOS 10.15+, tvOS 13.0+. +Represents the mutable state of diffable data source of UI.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public class DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
    + +
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+ + + + diff --git a/docs/badge.svg b/docs/badge.svg new file mode 100644 index 0000000..a096fec --- /dev/null +++ b/docs/badge.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + documentation + + + documentation + + + 100% + + + 100% + + + diff --git a/docs/css/highlight.css b/docs/css/highlight.css new file mode 100644 index 0000000..d0db0e1 --- /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 0000000..c83db5b --- /dev/null +++ b/docs/css/jazzy.css @@ -0,0 +1,368 @@ +*, *:before, *:after { + box-sizing: inherit; } + +body { + margin: 0; + background: #fff; + color: #333; + font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; + letter-spacing: .2px; + -webkit-font-smoothing: antialiased; + box-sizing: border-box; } + +h1 { + font-size: 2rem; + font-weight: 700; + margin: 1.275em 0 0.6em; } + +h2 { + font-size: 1.75rem; + font-weight: 700; + margin: 1.275em 0 0.3em; } + +h3 { + font-size: 1.5rem; + font-weight: 700; + margin: 1em 0 0.3em; } + +h4 { + font-size: 1.25rem; + font-weight: 700; + margin: 1.275em 0 0.85em; } + +h5 { + font-size: 1rem; + font-weight: 700; + margin: 1.275em 0 0.85em; } + +h6 { + font-size: 1rem; + font-weight: 700; + margin: 1.275em 0 0.85em; + color: #777; } + +p { + margin: 0 0 1em; } + +ul, ol { + padding: 0 0 0 2em; + margin: 0 0 0.85em; } + +blockquote { + margin: 0 0 0.85em; + padding: 0 15px; + color: #858585; + border-left: 4px solid #e5e5e5; } + +img { + max-width: 100%; } + +a { + color: #4183c4; + text-decoration: none; } + a:hover, a:focus { + outline: 0; + text-decoration: underline; } + +table { + background: #fff; + width: 100%; + border-collapse: collapse; + border-spacing: 0; + overflow: auto; + margin: 0 0 0.85em; } + +tr:nth-child(2n) { + background-color: #fbfbfb; } + +th, td { + padding: 6px 13px; + border: 1px solid #ddd; } + +pre { + margin: 0 0 1.275em; + padding: .85em 1em; + overflow: auto; + background: #f7f7f7; + font-size: .85em; + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } + +code { + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } + +p > code, li > code { + background: #f7f7f7; + padding: .2em; } + p > code:before, p > code:after, li > code:before, li > code:after { + letter-spacing: -.2em; + content: "\00a0"; } + +pre code { + padding: 0; + white-space: pre; } + +.content-wrapper { + display: flex; + flex-direction: column; } + @media (min-width: 768px) { + .content-wrapper { + flex-direction: row; } } + +.header { + display: flex; + padding: 8px; + font-size: 0.875em; + background: #444; + color: #999; } + +.header-col { + margin: 0; + padding: 0 8px; } + +.header-col--primary { + flex: 1; } + +.header-link { + color: #fff; } + +.header-icon { + padding-right: 6px; + vertical-align: -4px; + height: 16px; } + +.breadcrumbs { + font-size: 0.875em; + padding: 8px 16px; + margin: 0; + background: #fbfbfb; + border-bottom: 1px solid #ddd; } + +.carat { + height: 10px; + margin: 0 5px; } + +.navigation { + order: 2; } + @media (min-width: 768px) { + .navigation { + order: 1; + width: 25%; + max-width: 300px; + padding-bottom: 64px; + overflow: hidden; + word-wrap: normal; + background: #fbfbfb; + border-right: 1px solid #ddd; } } + +.nav-groups { + list-style-type: none; + padding-left: 0; } + +.nav-group-name { + border-bottom: 1px solid #ddd; + padding: 8px 0 8px 16px; } + +.nav-group-name-link { + color: #333; } + +.nav-group-tasks { + margin: 8px 0; + padding: 0 0 0 8px; } + +.nav-group-task { + font-size: 1em; + list-style-type: none; + white-space: nowrap; } + +.nav-group-task-link { + color: #808080; } + +.main-content { + order: 1; } + @media (min-width: 768px) { + .main-content { + order: 2; + flex: 1; + padding-bottom: 60px; } } + +.section { + padding: 0 32px; + border-bottom: 1px solid #ddd; } + +.section-content { + max-width: 834px; + margin: 0 auto; + padding: 16px 0; } + +.section-name { + color: #666; + display: block; } + +.declaration .highlight { + overflow-x: initial; + padding: 8px 0; + margin: 0; + background-color: transparent; + border: none; } + +.task-group-section { + border-top: 1px solid #ddd; } + +.task-group { + padding-top: 0px; } + +.task-name-container a[name]:before { + content: ""; + display: block; } + +.item-container { + padding: 0; } + +.item { + padding-top: 8px; + width: 100%; + list-style-type: none; } + .item a[name]:before { + content: ""; + display: block; } + .item .token { + padding-left: 3px; + margin-left: 0px; + font-size: 1rem; } + .item .declaration-note { + font-size: .85em; + color: #808080; + font-style: italic; } + +.pointer-container { + border-bottom: 1px solid #ddd; + left: -23px; + padding-bottom: 13px; + position: relative; + width: 110%; } + +.pointer { + left: 21px; + top: 7px; + display: block; + position: absolute; + width: 12px; + height: 12px; + border-left: 1px solid #ddd; + border-top: 1px solid #ddd; + background: #fff; + transform: rotate(45deg); } + +.height-container { + display: none; + position: relative; + width: 100%; + overflow: hidden; } + .height-container .section { + background: #fff; + border: 1px solid #ddd; + border-top-width: 0; + padding-top: 10px; + padding-bottom: 5px; + padding: 8px 16px; } + +.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: #4183c4; } + +.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 #ddd; + 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 { + padding: 8px 16px; + background: #444; + color: #ddd; + font-size: 0.8em; } + .footer p { + margin: 8px 0; } + .footer a { + color: #fff; } + +html.dash .header, html.dash .breadcrumbs, html.dash .navigation { + display: none; } +html.dash .height-container { + display: block; } + +form[role=search] input { + font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 24px; + padding: 0 10px; + margin: 0; + border: none; + border-radius: 1em; } + .loading form[role=search] input { + background: white url(../img/spinner.gif) center right 4px no-repeat; } +form[role=search] .tt-menu { + margin: 0; + min-width: 300px; + background: #fbfbfb; + color: #333; + border: 1px solid #ddd; } +form[role=search] .tt-highlight { + font-weight: bold; } +form[role=search] .tt-suggestion { + font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 0 8px; } + form[role=search] .tt-suggestion span { + display: table-cell; + white-space: nowrap; } + form[role=search] .tt-suggestion .doc-parent-name { + width: 100%; + text-align: right; + font-weight: normal; + font-size: 0.9em; + padding-left: 16px; } +form[role=search] .tt-suggestion:hover, +form[role=search] .tt-suggestion.tt-cursor { + cursor: pointer; + background-color: #4183c4; + color: #fff; } +form[role=search] .tt-suggestion:hover .doc-parent-name, +form[role=search] .tt-suggestion.tt-cursor .doc-parent-name { + color: #fff; } diff --git a/docs/img/carat.png b/docs/img/carat.png new file mode 100755 index 0000000..29d2f7f 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 0000000..6f694c7 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 0000000..628da97 Binary files /dev/null and b/docs/img/gh.png differ diff --git a/docs/img/spinner.gif b/docs/img/spinner.gif new file mode 100644 index 0000000..e3038d0 Binary files /dev/null and b/docs/img/spinner.gif differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..70fb22c --- /dev/null +++ b/docs/index.html @@ -0,0 +1,293 @@ + + + + DiffableDataSources Reference + + + + + + + + + + + + + + + +
+

+ + DiffableDataSources Docs + + (100% documented) +

+ +

+

+ +
+

+ +

+ + + View on GitHub + +

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

+DiffableDataSources +

+ +

+💾 A library for backporting UITableView/UICollectionViewDiffableDataSource
+powered by DifferenceKit. +

+ +

+Swift5 +Release +CocoaPods +Carthage +Swift Package Manager +
+Build Status +Platform +Lincense +

+ +

+Made with ❤️ by Ryo Aoyama +

+ +
+

Introduction

+ +

+

+ +

Apple has announced a diffable data source at WWDC 2019.
+It’s a great API that easily updating our table view and collection view items using automatic diffing.
+However, it’s a little while before we can use it in a production service.
+That because it requires the latest OS to use.
+DiffableDataSources make it possible to introduce almost the same functionality from now on.

+ +

Uses a sophisticated open source DifferenceKit for the algorithm engine.
+It’s extremely fast and completely avoids synchronization bugs, exceptions, and crashes.

+ +


+ +
+

Difference from the Official

+

Spec

+ +
    +
  • Supports iOS 9.0+ / macOS 10.11+ / tvOS 9.0+
  • +
  • Open sourced algorithm.
  • +
  • Duplicate sections or items are allowed.
  • +
  • Using performBatchUpdates for diffing updates.
  • +
+

Namings

+ +

DiffableDataSources have different class names to avoid conflicts with the official API.
+Correspondence table is below.

+ + + + + + + + + + + + + + + + + + + + + + + +
OfficialBackported
NSDiffableDataSourceSnapshotDiffableDataSourceSnapshot
UITableViewDiffableDataSourceTableViewDiffableDataSource
UICollectionViewDiffableDataSourceCollectionViewDiffableDataSource
NSCollectionViewDiffableDataSourceCocoaCollectionViewDiffableDataSource
+ +
+

Getting Started

+ + +

Build Project

+
$ git clone https://github.com/ra1028/DiffableDataSources.git
+$ cd DiffableDataSources/
+$ make setup
+$ open DiffableDataSources.xcworkspace
+
+ +
+

Basic Usage

+ +

First, define the type representing section.
+It should conforms to Hashable for identifies from the all sections.
+Type of enum can used conveniently befause it conforms Hashable by default.

+
enum Section {
+    case main
+}
+
+ +

Then, define the item type conforms to Hashable.

+
struct User: Hashable {
+    var name: String
+}
+
+ +

Create a data source object, it will be set to table view automatically.
+You should dequeue the non nil cells via closure.

+
final class UsersViewController: UIViewController {
+    let tableView: UITableView = ...
+
+    lazy var dataSource = TableViewDiffableDataSource<Section, User>(tableView: tableView) { tableView, indexPath, user in
+        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+        cell.textLabel?.text = user.name
+        return cell
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
+    }
+}
+
+ +

Manages and updates the data sources intuitively by intermediating DiffableDataSourceSnapshot.
+The UI isn’t updated until you apply the edited snapshot object.
+Update the UI with diffing animation automatically calculated by applying an edited snapshot.

+
let users = [
+    User(name: "Steve Jobs"),
+    User(name: "Stephen Wozniak"),
+    User(name: "Tim Cook"),
+    User(name: "Jonathan Ive")
+]
+
+let snapshot = DiffableDataSourceSnapshot<Section, User>()
+snapshot.appendSections([.main])
+snapshot.appendItems(users)
+
+dataSource.apply(snapshot)
+
+ +

Check the documentation for more detailed API.

+ +

+[See More Usage] +

+ +
+

Requirements

+ +
    +
  • Swift 5.0+
  • +
  • iOS 9.0+
  • +
  • macOS 10.11+
  • +
  • tvOS 9.0+
  • +
+ +
+

Installation

+

CocoaPods

+ +

Add the following to your Podfile:

+
pod 'DiffableDataSources'
+
+

Carthage

+ +

Add the following to your Cartfile:

+
github "ra1028/DiffableDataSources"
+
+

Swift Package Manager

+ +

Add the following to the dependencies of your Package.swift:

+
.package(url: "https://github.com/ra1028/DiffableDataSources.git", from: "x.x.x")
+
+ +
+

Contributing

+ +

Pull requests, bug reports and feature requests are welcome 🚀
+Please see the CONTRIBUTING file for learn how to contribute to DiffableDataSources.

+ +
+

Relations

+

DifferenceKit

+ +

A fast and flexible O(n) difference algorithm framework for Swift collection.

+

Carbon

+ +

A declarative library for building component-based user interfaces in UITableView and UICollectionView.

+ +
+

License

+ +

DiffableDataSources is released under the Apache 2.0 License.

+ +
+
+ + +
+
+ + + + diff --git a/docs/js/jazzy.js b/docs/js/jazzy.js new file mode 100755 index 0000000..009c80d --- /dev/null +++ b/docs/js/jazzy.js @@ -0,0 +1,43 @@ +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; + $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(); +}); + +// Dumb down quotes within code blocks that delimit strings instead of quotations +// https://github.com/realm/jazzy/issues/714 +$("code q").replaceWith(function () { + return ["\"", $(this).contents(), "\""]; +}); diff --git a/docs/js/jazzy.search.js b/docs/js/jazzy.search.js new file mode 100644 index 0000000..54be83c --- /dev/null +++ b/docs/js/jazzy.search.js @@ -0,0 +1,62 @@ +$(function(){ + var searchIndex = lunr(function() { + this.ref('url'); + this.field('name'); + }); + + var $typeahead = $('[data-typeahead]'); + var $form = $typeahead.parents('form'); + var searchURL = $form.attr('action'); + + function displayTemplate(result) { + return result.name; + } + + function suggestionTemplate(result) { + var t = '
'; + t += '' + result.name + ''; + if (result.parent_name) { + t += '' + result.parent_name + ''; + } + t += '
'; + return t; + } + + $typeahead.one('focus', function() { + $form.addClass('loading'); + + $.getJSON(searchURL).then(function(searchData) { + $.each(searchData, function (url, doc) { + searchIndex.add({url: url, name: doc.name}); + }); + + $typeahead.typeahead( + { + highlight: true, + minLength: 3 + }, + { + limit: 10, + display: displayTemplate, + templates: { suggestion: suggestionTemplate }, + source: function(query, sync) { + var results = searchIndex.search(query).map(function(result) { + var doc = searchData[result.ref]; + doc.url = result.ref; + return doc; + }); + sync(results); + } + } + ); + $form.removeClass('loading'); + $typeahead.trigger('focus'); + }); + }); + + var baseURL = searchURL.slice(0, -"search.json".length); + + $typeahead.on('typeahead:select', function(e, result) { + window.location = baseURL + result.url; + }); +}); diff --git a/docs/js/jquery.min.js b/docs/js/jquery.min.js new file mode 100755 index 0000000..ab28a24 --- /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("