diff --git a/CHANGELOG.md b/CHANGELOG.md index ff4b9849b..a64f697a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,17 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instagram/IGListKit/releases) on GitHub. -## Master +1.1.0 +----- + +### Enhancements -- Fixed `-[IGListAdapter reloadDataWithCompletion:]` not returning early when `collectionView` or `dataSource` is nil and `completion` is nil. [Ben Asher](https://github.com/benasher44) [#51](https://github.com/Instagram/IGListKit/pull/51) +- Added support for cells created from nibs. [Sven Bacia](https://github.com/svenbacia) [(#56)](https://github.com/Instagram/IGListKit/pull/56) +- Added an additional initializer for `IGListSingleSectionController` to be able to support single sections created from nibs. An example can be found [here](Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift). +- Fixed `-[IGListAdapter reloadDataWithCompletion:]` not returning early when `collectionView` or `dataSource` is nil and `completion` is nil. [Ben Asher](https://github.com/benasher44) [(#51)](https://github.com/Instagram/IGListKit/pull/51) - Added `-isFirstSection` and `-isLastSection` APIs to `IGListSectionController` + 1.0.0 ----- diff --git a/Example/IGListKitExamples.xcodeproj/project.pbxproj b/Example/IGListKitExamples.xcodeproj/project.pbxproj index f2e9855bf..615c4aa45 100644 --- a/Example/IGListKitExamples.xcodeproj/project.pbxproj +++ b/Example/IGListKitExamples.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 26271C8E1DAE9D3F0073E116 /* SingleSectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26271C8D1DAE9D3F0073E116 /* SingleSectionViewController.swift */; }; + 26271C921DAE9EFC0073E116 /* NibCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 26271C911DAE9EFC0073E116 /* NibCell.xib */; }; + 26271C941DAE9F050073E116 /* NibCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26271C931DAE9F050073E116 /* NibCell.swift */; }; 2942FF8C1D9F39E00015D24B /* DemoSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF831D9F39E00015D24B /* DemoSectionController.swift */; }; 2942FF8D1D9F39E00015D24B /* EmbeddedSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF841D9F39E00015D24B /* EmbeddedSectionController.swift */; }; 2942FF8E1D9F39E00015D24B /* ExpandableSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942FF851D9F39E00015D24B /* ExpandableSectionController.swift */; }; @@ -37,6 +40,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 26271C8D1DAE9D3F0073E116 /* SingleSectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleSectionViewController.swift; sourceTree = ""; }; + 26271C911DAE9EFC0073E116 /* NibCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NibCell.xib; sourceTree = ""; }; + 26271C931DAE9F050073E116 /* NibCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibCell.swift; sourceTree = ""; }; 2942FF831D9F39E00015D24B /* DemoSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoSectionController.swift; sourceTree = ""; }; 2942FF841D9F39E00015D24B /* EmbeddedSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedSectionController.swift; sourceTree = ""; }; 2942FF851D9F39E00015D24B /* ExpandableSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpandableSectionController.swift; sourceTree = ""; }; @@ -148,6 +154,7 @@ 299068271D75BFEC00A62888 /* MixedDataViewController.swift */, 2991F9231D7BB89F00B0C58F /* NestedAdapterViewController.swift */, 299B53FF1D6BD6630074A202 /* SearchViewController.swift */, + 26271C8D1DAE9D3F0073E116 /* SingleSectionViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -162,6 +169,8 @@ 2991F92B1D7BBE5400B0C58F /* RemoveCell.swift */, 2961B3AF1D68B28E001C9451 /* SearchCell.swift */, 2961B3A81D68B0B5001C9451 /* SpinnerCell.swift */, + 26271C931DAE9F050073E116 /* NibCell.swift */, + 26271C911DAE9EFC0073E116 /* NibCell.xib */, ); path = Views; sourceTree = ""; @@ -247,6 +256,7 @@ buildActionMask = 2147483647; files = ( 2961B3981D68B031001C9451 /* LaunchScreen.storyboard in Resources */, + 26271C921DAE9EFC0073E116 /* NibCell.xib in Resources */, 2961B3951D68B031001C9451 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -296,7 +306,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -314,6 +324,7 @@ 2942FF931D9F39E00015D24B /* SearchSectionController.swift in Sources */, 2942FF911D9F39E00015D24B /* LabelSectionController.swift in Sources */, 2961B3AC1D68B0B5001C9451 /* LoadMoreViewController.swift in Sources */, + 26271C941DAE9F050073E116 /* NibCell.swift in Sources */, 2991F9191D7BADC900B0C58F /* CenterLabelCell.swift in Sources */, 29628F141D91905A0026B15A /* DetailLabelCell.swift in Sources */, 2991F9301D7BC0E400B0C58F /* EmptyViewController.swift in Sources */, @@ -326,6 +337,7 @@ 2991F9281D7BB9EC00B0C58F /* EmbeddedCollectionViewCell.swift in Sources */, 2942FF8F1D9F39E00015D24B /* GridSectionController.swift in Sources */, 2942FF921D9F39E00015D24B /* RemoveSectionController.swift in Sources */, + 26271C8E1DAE9D3F0073E116 /* SingleSectionViewController.swift in Sources */, 2961B3AD1D68B0B5001C9451 /* LabelCell.swift in Sources */, 2942FF901D9F39E00015D24B /* HorizontalSectionController.swift in Sources */, 2961B3AB1D68B0B5001C9451 /* DemosViewController.swift in Sources */, diff --git a/Example/IGListKitExamples/ViewControllers/DemosViewController.swift b/Example/IGListKitExamples/ViewControllers/DemosViewController.swift index b6d08d8a9..d8223fe15 100644 --- a/Example/IGListKitExamples/ViewControllers/DemosViewController.swift +++ b/Example/IGListKitExamples/ViewControllers/DemosViewController.swift @@ -27,7 +27,8 @@ class DemosViewController: UIViewController, IGListAdapterDataSource { DemoItem(name: "Search Autocomplete", controllerClass: SearchViewController.self), DemoItem(name: "Mixed Data", controllerClass: MixedDataViewController.self), DemoItem(name: "Nested Adapter", controllerClass: NestedAdapterViewController.self), - DemoItem(name: "Empty View", controllerClass: EmptyViewController.self) + DemoItem(name: "Empty View", controllerClass: EmptyViewController.self), + DemoItem(name: "Single Section Controller", controllerClass: SingleSectionViewController.self) ] override func viewDidLoad() { diff --git a/Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift b/Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift new file mode 100644 index 000000000..f1f118a3c --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift @@ -0,0 +1,84 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit +import IGListKit + +final class SingleSectionViewController: UIViewController, IGListAdapterDataSource, IGListSingleSectionControllerDelegate { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + + let collectionView = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout()) + + var data = [Int]() + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + for index in 0..<20 { + data.append(index) + } + + view.addSubview(collectionView) + adapter.collectionView = collectionView + adapter.dataSource = self + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.frame = view.bounds + } + + //MARK: - IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + return data as [IGListDiffable] + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + let configureBlock = { (data: Any, cell: UICollectionViewCell) in + guard let cell = cell as? NibCell, let number = data as? Int else { return } + cell.textLabel.text = "Cell: \(number + 1)" + } + + let sizeBlock = { (context: IGListCollectionContext) -> CGSize in + return CGSize(width: context.containerSize.width, height: 44) + } + let sectionController = IGListSingleSectionController(nibName: NibCell.nibName, + bundle: nil, + configureBlock: configureBlock, + sizeBlock: sizeBlock) + sectionController.selectionDelegate = self + + return sectionController + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { + return nil + } + + // MARK: - IGListSingleSectionControllerDelegate + + func didSelect(_ sectionController: IGListSingleSectionController) { + let section = adapter.section(for: sectionController) + 1 + let alert = UIAlertController(title: "Section \(section) was selected 🎉", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + +} diff --git a/Example/IGListKitExamples/Views/NibCell.swift b/Example/IGListKitExamples/Views/NibCell.swift new file mode 100644 index 000000000..bf643d3ec --- /dev/null +++ b/Example/IGListKitExamples/Views/NibCell.swift @@ -0,0 +1,22 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit + +final class NibCell: UICollectionViewCell { + static let nibName = "NibCell" + @IBOutlet var textLabel: UILabel! +} + + diff --git a/Example/IGListKitExamples/Views/NibCell.xib b/Example/IGListKitExamples/Views/NibCell.xib new file mode 100644 index 000000000..664ce3e3d --- /dev/null +++ b/Example/IGListKitExamples/Views/NibCell.xib @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/IGListKitExamples/Views/SpinnerCell.swift b/Example/IGListKitExamples/Views/SpinnerCell.swift index 04c55b6fc..7e1e2579c 100644 --- a/Example/IGListKitExamples/Views/SpinnerCell.swift +++ b/Example/IGListKitExamples/Views/SpinnerCell.swift @@ -20,7 +20,9 @@ func spinnerSectionController() -> IGListSingleSectionController { let sizeBlock = { (context: IGListCollectionContext) -> CGSize in return CGSize(width: context.containerSize.width, height: 100) } - return IGListSingleSectionController(cellClass: SpinnerCell.self, configureBlock: configureBlock, sizeBlock: sizeBlock) + return IGListSingleSectionController(cellClass: SpinnerCell.self, + configureBlock: configureBlock, + sizeBlock: sizeBlock) } class SpinnerCell: UICollectionViewCell { diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 9a00e6256..6018bbd37 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - IGListKit (1.0) + - IGListKit (1.0.0) DEPENDENCIES: - IGListKit (from `../IGListKit.podspec`) @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: ../IGListKit.podspec SPEC CHECKSUMS: - IGListKit: 67493f1f775fea949596c7fc8317c7bd77ba8539 + IGListKit: 3242c475fa77e2abf2d4de319703088008fbe9b9 PODFILE CHECKSUM: 4bdfb42d1e7b2b121bf8c83bc300d7d77aa0fc3a -COCOAPODS: 1.0.1 +COCOAPODS: 1.1.0.rc.3 diff --git a/Example/Pods/Local Podspecs/IGListKit.podspec.json b/Example/Pods/Local Podspecs/IGListKit.podspec.json index 6c011ca13..04de4c67e 100644 --- a/Example/Pods/Local Podspecs/IGListKit.podspec.json +++ b/Example/Pods/Local Podspecs/IGListKit.podspec.json @@ -1,10 +1,10 @@ { "name": "IGListKit", - "version": "1.0", + "version": "1.0.0", "summary": "A data-driven UICollectionView framework.", "homepage": "https://github.com/Instagram/IGListKit", - "documentation_url": "TODO", - "description": "Create data-driven feeds backed by UICollectionView that efficiently diff and update.", + "documentation_url": "https://instagram.github.io/IGListKit", + "description": "A data-driven UICollectionView framework for building fast and flexible lists.", "license": { "type": "BSD" }, @@ -12,7 +12,8 @@ "social_media_url": "https://twitter.com/fbOpenSource", "source": { "git": "https://github.com/Instagram/IGListKit.git", - "tag": "1.0" + "tag": "1.0.0", + "branch": "stable" }, "source_files": "Source/**/*.{h,m,mm}", "private_header_files": "Source/Internal/*.h", diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock index 9a00e6256..6018bbd37 100644 --- a/Example/Pods/Manifest.lock +++ b/Example/Pods/Manifest.lock @@ -1,5 +1,5 @@ PODS: - - IGListKit (1.0) + - IGListKit (1.0.0) DEPENDENCIES: - IGListKit (from `../IGListKit.podspec`) @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: ../IGListKit.podspec SPEC CHECKSUMS: - IGListKit: 67493f1f775fea949596c7fc8317c7bd77ba8539 + IGListKit: 3242c475fa77e2abf2d4de319703088008fbe9b9 PODFILE CHECKSUM: 4bdfb42d1e7b2b121bf8c83bc300d7d77aa0fc3a -COCOAPODS: 1.0.1 +COCOAPODS: 1.1.0.rc.3 diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index 96dd5915f..ea358414c 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -546,13 +546,99 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 044E174FC9DBD0528A6A23546D3945FB /* Release */ = { + 015A368F878AC3E2CEAE21DDE8026304 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGNING_REQUIRED = NO; + COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + ONLY_ACTIVE_ARCH = YES; + PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + 44CDBB6D11DE06DB64D6268622BDC47E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGNING_REQUIRED = NO; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6D8BEF09A2BC418CD8BBAB86D254BC18 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 891CE36E66A66905E25AD89B001B5648 /* IGListKit.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -565,21 +651,24 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = "Target Support Files/IGListKit/IGListKit.modulemap"; - MTL_ENABLE_DEBUG_INFO = NO; + MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = IGListKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Release; + name = Debug; }; - 419C2F52156B7DC129FD76F28DF37A3B /* Release */ = { + 81B111721A36CCF4CD5432B606E638FE /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = A198E265B2C6E673C7C9C5050F92D9F0 /* Pods-IGListKitExamples.release.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -608,53 +697,13 @@ }; name = Release; }; - 47BEF9D903506B003EA5C2B249729489 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_DEBUG=1", - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - ONLY_ACTIVE_ARCH = YES; - STRIP_INSTALLED_PRODUCT = NO; - SYMROOT = "${SRCROOT}/../build"; - }; - name = Debug; - }; - 8A88E3170224FBEF18E2A77F2C52A3C1 /* Debug */ = { + C197420E85C6EB8A3C2C625941CB7B61 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 1B2B14BA3AB402D9CC387EEB27A2F746 /* Pods-IGListKitExamples.debug.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; @@ -683,51 +732,15 @@ }; name = Debug; }; - AAF678CED40D3499169D10F63CA0719E /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_RELEASE=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - STRIP_INSTALLED_PRODUCT = NO; - SYMROOT = "${SRCROOT}/../build"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - DCC9B305BBAAE5AF1B18323D97C89118 /* Debug */ = { + EAC7D6220A71D1FDCB6309C89D9A46AD /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 891CE36E66A66905E25AD89B001B5648 /* IGListKit.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -740,15 +753,16 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = "Target Support Files/IGListKit/IGListKit.modulemap"; - MTL_ENABLE_DEBUG_INFO = YES; + MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = IGListKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Debug; + name = Release; }; /* End XCBuildConfiguration section */ @@ -756,8 +770,8 @@ 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( - 47BEF9D903506B003EA5C2B249729489 /* Debug */, - AAF678CED40D3499169D10F63CA0719E /* Release */, + 015A368F878AC3E2CEAE21DDE8026304 /* Debug */, + 44CDBB6D11DE06DB64D6268622BDC47E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -765,8 +779,8 @@ 535D892140575DCAAE9094BF475E7520 /* Build configuration list for PBXNativeTarget "IGListKit" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCC9B305BBAAE5AF1B18323D97C89118 /* Debug */, - 044E174FC9DBD0528A6A23546D3945FB /* Release */, + 6D8BEF09A2BC418CD8BBAB86D254BC18 /* Debug */, + EAC7D6220A71D1FDCB6309C89D9A46AD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -774,8 +788,8 @@ D5ABECD9BBE4FF7324D45ECEE77ED0B8 /* Build configuration list for PBXNativeTarget "Pods-IGListKitExamples" */ = { isa = XCConfigurationList; buildConfigurations = ( - 8A88E3170224FBEF18E2A77F2C52A3C1 /* Debug */, - 419C2F52156B7DC129FD76F28DF37A3B /* Release */, + C197420E85C6EB8A3C2C625941CB7B61 /* Debug */, + 81B111721A36CCF4CD5432B606E638FE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Example/Pods/Target Support Files/IGListKit/IGListKit-umbrella.h b/Example/Pods/Target Support Files/IGListKit/IGListKit-umbrella.h index eb071b2ab..6760a4149 100644 --- a/Example/Pods/Target Support Files/IGListKit/IGListKit-umbrella.h +++ b/Example/Pods/Target Support Files/IGListKit/IGListKit-umbrella.h @@ -1,4 +1,6 @@ +#ifdef __OBJC__ #import +#endif #import "IGListAdapter.h" #import "IGListAdapterDataSource.h" diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.markdown index 92ddd3fac..2c79cadac 100644 --- a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.markdown +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.markdown @@ -5,7 +5,7 @@ This application makes use of the following third party libraries: BSD License -For IGListKit software +For `IGListKit` software Copyright (c) 2016, Facebook, Inc. All rights reserved. diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.plist index fd00e358a..cf1beb3f9 100644 --- a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.plist +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-acknowledgements.plist @@ -16,7 +16,7 @@ FooterText BSD License -For IGListKit software +For `IGListKit` software Copyright (c) 2016, Facebook, Inc. All rights reserved. @@ -45,6 +45,8 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + License + BSD Title IGListKit Type diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-resources.sh b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-resources.sh index 0a1561528..25e9d3775 100755 --- a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-resources.sh +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-resources.sh @@ -23,12 +23,6 @@ case "${TARGETED_DEVICE_FAMILY}" in ;; esac -realpath() { - DIRECTORY="$(cd "${1%/*}" && pwd)" - FILENAME="${1##*/}" - echo "$DIRECTORY/$FILENAME" -} - install_resource() { if [[ "$1" = /* ]] ; then @@ -70,7 +64,7 @@ EOM xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" ;; *.xcassets) - ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH") + ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") ;; *) @@ -93,7 +87,7 @@ then # Find all other xcassets (this unfortunately includes those of path pods and other targets). OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) while read line; do - if [[ $line != "`realpath $PODS_ROOT`*" ]]; then + if [[ $line != "${PODS_ROOT}*" ]]; then XCASSET_FILES+=("$line") fi done <<<"$OTHER_XCASSETS" diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-umbrella.h b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-umbrella.h index 26ee99a48..7d2e33891 100644 --- a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-umbrella.h +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples-umbrella.h @@ -1,4 +1,6 @@ +#ifdef __OBJC__ #import +#endif FOUNDATION_EXPORT double Pods_IGListKitExamplesVersionNumber; diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig index 39a8e31f8..76789ae5c 100644 --- a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig @@ -1,3 +1,4 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' diff --git a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig index 39a8e31f8..76789ae5c 100644 --- a/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig +++ b/Example/Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig @@ -1,3 +1,4 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' diff --git a/IGListKit.xcodeproj/project.pbxproj b/IGListKit.xcodeproj/project.pbxproj index b98614975..37e2ae11c 100644 --- a/IGListKit.xcodeproj/project.pbxproj +++ b/IGListKit.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 26271C8A1DAE94E40073E116 /* IGTestSingleNibItemDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 26271C891DAE94E40073E116 /* IGTestSingleNibItemDataSource.m */; }; + 26271C8C1DAE96740073E116 /* IGListSingleNibItemControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 26271C8B1DAE96740073E116 /* IGListSingleNibItemControllerTests.m */; }; + 294369B11DB1B7AE0025F6E7 /* IGTestNibCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 294369B01DB1B7AE0025F6E7 /* IGTestNibCell.xib */; }; 296176F71D9D54C100F40F34 /* IGListScrollDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 296176EF1D9D54C100F40F34 /* IGListScrollDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 296176F81D9D54C100F40F34 /* IGListSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = 296176F01D9D54C100F40F34 /* IGListSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 296176F91D9D54C100F40F34 /* IGListSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 296176F11D9D54C100F40F34 /* IGListSectionController.m */; }; @@ -108,6 +111,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 26271C881DAE94E40073E116 /* IGTestSingleNibItemDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestSingleNibItemDataSource.h; sourceTree = ""; }; + 26271C891DAE94E40073E116 /* IGTestSingleNibItemDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestSingleNibItemDataSource.m; sourceTree = ""; }; + 26271C8B1DAE96740073E116 /* IGListSingleNibItemControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListSingleNibItemControllerTests.m; sourceTree = ""; }; + 294369B01DB1B7AE0025F6E7 /* IGTestNibCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IGTestNibCell.xib; sourceTree = ""; }; 296176EF1D9D54C100F40F34 /* IGListScrollDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListScrollDelegate.h; sourceTree = ""; }; 296176F01D9D54C100F40F34 /* IGListSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListSectionController.h; sourceTree = ""; }; 296176F11D9D54C100F40F34 /* IGListSectionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListSectionController.m; sourceTree = ""; }; @@ -235,6 +242,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 294369AF1DB1B7AE0025F6E7 /* Assets */ = { + isa = PBXGroup; + children = ( + 294369B01DB1B7AE0025F6E7 /* IGTestNibCell.xib */, + ); + path = Assets; + sourceTree = ""; + }; 2F54C5CD9AC0E162B7763498 /* Pods */ = { isa = PBXGroup; children = ( @@ -265,6 +280,8 @@ 88144F001D870EDC007C7F66 /* IGTestObject.m */, 88144F011D870EDC007C7F66 /* IGTestSingleItemDataSource.h */, 88144F021D870EDC007C7F66 /* IGTestSingleItemDataSource.m */, + 26271C881DAE94E40073E116 /* IGTestSingleNibItemDataSource.h */, + 26271C891DAE94E40073E116 /* IGTestSingleNibItemDataSource.m */, 88144F031D870EDC007C7F66 /* IGTestStackedDataSource.h */, 88144F041D870EDC007C7F66 /* IGTestStackedDataSource.m */, 88144F051D870EDC007C7F66 /* IGTestSupplementarySource.h */, @@ -375,6 +392,7 @@ 887D0B551D870E1E009E01F7 /* Tests */ = { isa = PBXGroup; children = ( + 294369AF1DB1B7AE0025F6E7 /* Assets */, 88144EE21D870EDC007C7F66 /* IGListAdapterE2ETests.m */, 88144EE31D870EDC007C7F66 /* IGListAdapterTests.m */, 88144EE41D870EDC007C7F66 /* IGListAdapterUpdaterTests.m */, @@ -386,6 +404,7 @@ 88144EEB1D870EDC007C7F66 /* IGListKitTests-Bridging-Header.h */, 88144EEC1D870EDC007C7F66 /* IGListObjectMapTests.m */, 88144EED1D870EDC007C7F66 /* IGListSingleItemControllerTests.m */, + 26271C8B1DAE96740073E116 /* IGListSingleNibItemControllerTests.m */, 88144EEE1D870EDC007C7F66 /* IGListStackItemControllerTests.m */, 88144EEF1D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m */, 887D0B571D870E1E009E01F7 /* Info.plist */, @@ -542,6 +561,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 294369B11DB1B7AE0025F6E7 /* IGTestNibCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -649,6 +669,7 @@ 88144F191D870EDC007C7F66 /* IGTestDelegateDataSource.m in Sources */, 88144F0C1D870EDC007C7F66 /* IGListDiffTests.m in Sources */, 88144F0A1D870EDC007C7F66 /* IGListBatchUpdateDataTests.m in Sources */, + 26271C8C1DAE96740073E116 /* IGListSingleNibItemControllerTests.m in Sources */, 88144F101D870EDC007C7F66 /* IGListSingleItemControllerTests.m in Sources */, 88144F121D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m in Sources */, 88144F151D870EDC007C7F66 /* IGListTestSection.m in Sources */, @@ -657,6 +678,7 @@ 88144F161D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.m in Sources */, 88144F091D870EDC007C7F66 /* IGListAdapterUpdaterTests.m in Sources */, 88144F0F1D870EDC007C7F66 /* IGListObjectMapTests.m in Sources */, + 26271C8A1DAE94E40073E116 /* IGTestSingleNibItemDataSource.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m index 5a088e34a..68ce31384 100644 --- a/Source/IGListAdapter.m +++ b/Source/IGListAdapter.m @@ -72,6 +72,7 @@ - (void)setCollectionView:(IGListCollectionView *)collectionView { // dump old registered section controllers in the case that we are changing collection views or setting for // the first time _registeredCellClasses = [NSMutableSet new]; + _registeredNibNames = [NSMutableSet new]; _registeredSupplementaryViewIdentifiers = [NSMutableSet new]; _collectionView = collectionView; @@ -716,7 +717,7 @@ - (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass IGParameterAssert(cellClass != nil); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Reloading adapter without a collection view."); - NSString *identifier = IGListReusableViewIdentifier(cellClass, nil); + NSString *identifier = IGListReusableViewIdentifier(cellClass, nil, nil); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index]; if (![self.registeredCellClasses containsObject:cellClass]) { [self.registeredCellClasses addObject:cellClass]; @@ -725,6 +726,23 @@ - (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath]; } +- (UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName + bundle:(NSBundle *)bundle + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index { + IGAssertMainThread(); + IGParameterAssert(sectionController != nil); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Reloading adapter without a collection view."); + NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index]; + if (![self.registeredNibNames containsObject:nibName]) { + [self.registeredNibNames addObject:nibName]; + UINib *nib = [UINib nibWithNibName:nibName bundle:bundle]; + [collectionView registerNib:nib forCellWithReuseIdentifier:nibName]; + } + return [collectionView dequeueReusableCellWithReuseIdentifier:nibName forIndexPath:indexPath]; +} + - (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind forSectionController:(IGListSectionController *)sectionController class:(Class)viewClass @@ -732,7 +750,7 @@ - (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(N IGAssertMainThread(); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Reloading adapter without a collection view."); - NSString *identifier = IGListReusableViewIdentifier(viewClass, elementKind); + NSString *identifier = IGListReusableViewIdentifier(viewClass, nil, elementKind); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index]; if (![self.registeredSupplementaryViewIdentifiers containsObject:identifier]) { [self.registeredSupplementaryViewIdentifiers addObject:identifier]; diff --git a/Source/IGListCollectionContext.h b/Source/IGListCollectionContext.h index 5869b39e8..aeaf8db88 100644 --- a/Source/IGListCollectionContext.h +++ b/Source/IGListCollectionContext.h @@ -91,6 +91,23 @@ NS_ASSUME_NONNULL_BEGIN forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; +/** + Dequeues a cell from the UICollectionView reuse pool. + + @param nibName The name of the nib file. + @param bundle The bundle in which to search for the nib file. If nil, this method looks for the nib file in the main bundle. + @param sectionController The section controller requesting this information. + @param index The index of the cell. + + @return A cell dequeued from the reuse pool or newly created. + + @note This method uses a string representation of the cell class as the identifier. + */ +- (__kindof UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName + bundle:(nullable NSBundle *)bundle + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index; + /** Dequeues a supplementary view from the UICollectionView reuse pool. diff --git a/Source/IGListSingleSectionController.h b/Source/IGListSingleSectionController.h index 71a724f37..3ea83c578 100644 --- a/Source/IGListSingleSectionController.h +++ b/Source/IGListSingleSectionController.h @@ -15,6 +15,9 @@ NS_ASSUME_NONNULL_BEGIN +typedef void (^IGListSingleSectionCellConfigureBlock)(id item, __kindof UICollectionViewCell *cell); +typedef CGSize (^IGListSingleSectionCellSizeBlock)(id _Nullable collectionContext); + @class IGListSingleSectionController; /** @@ -41,19 +44,37 @@ IGLK_SUBCLASSING_RESTRICTED /** Create a new section controller for a given cell type that will always have only one cell when present in a feed. - + @param cellClass The UICollectionViewCell subclass for the single cell. @param configureBlock A block that configures the cell with the item given to the section controller. @param sizeBlock A block that returns the size for the cell given the collection context. - + @return A new section controller. - + @warning Be VERY CAREFUL not to create retain cycles by holding strong references to: the object that owns the adapter (usually "self") or the IGListAdapter. Pass in locally scoped objects or use weak references! */ - (instancetype)initWithCellClass:(Class)cellClass - configureBlock:(void (^)(id item, __kindof UICollectionViewCell *cell))configureBlock - sizeBlock:(CGSize (^)(id collectionContext))sizeBlock NS_DESIGNATED_INITIALIZER; + configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock + sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock; + +/** + Create a new section controller for a given nib name and bundle that will always have only one cell when present in a feed. + + @param nibName The name of the nib file for the single cell. + @param bundle The bundle in which to search for the nib file. If nil, this method looks for the file in the main bundle. + @param configureBlock A block that configures the cell with the item given to the section controller. + @param sizeBlock A block that returns the size for the cell given the collection context. + + @return A new section controller. + + @warning Be VERY CAREFUL not to create retain cycles by holding strong references to: the object that owns the adapter + (usually "self") or the IGListAdapter. Pass in locally scoped objects or use weak references! + */ +- (instancetype)initWithNibName:(NSString *)nibName + bundle:(nullable NSBundle *)bundle + configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock + sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock; /** An optional delegate that handles selection and deselection. diff --git a/Source/IGListSingleSectionController.m b/Source/IGListSingleSectionController.m index 8087156d8..26109d6ca 100644 --- a/Source/IGListSingleSectionController.m +++ b/Source/IGListSingleSectionController.m @@ -13,9 +13,11 @@ @interface IGListSingleSectionController () +@property (nonatomic, strong, readonly) NSString *nibName; +@property (nonatomic, strong, readonly) NSBundle *bundle; @property (nonatomic, strong, readonly) Class cellClass; -@property (nonatomic, strong, readonly) void (^configureBlock)(id, __kindof UICollectionViewCell *); -@property (nonatomic, strong, readonly) CGSize (^sizeBlock)(id); +@property (nonatomic, strong, readonly) IGListSingleSectionCellConfigureBlock configureBlock; +@property (nonatomic, strong, readonly) IGListSingleSectionCellSizeBlock sizeBlock; @property (nonatomic, strong) id item; @@ -24,8 +26,8 @@ @interface IGListSingleSectionController () @implementation IGListSingleSectionController - (instancetype)initWithCellClass:(Class)cellClass - configureBlock:(void (^)(id, __kindof UICollectionViewCell *))configureBlock - sizeBlock:(CGSize (^)(id))sizeBlock { + configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock + sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock { IGParameterAssert(cellClass != nil); IGParameterAssert(configureBlock != nil); if (self = [super init]) { @@ -36,6 +38,21 @@ - (instancetype)initWithCellClass:(Class)cellClass return self; } +- (instancetype)initWithNibName:(NSString *)nibName + bundle:(NSBundle *)bundle + configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock + sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock { + IGParameterAssert(nibName != nil); + IGParameterAssert(configureBlock != nil); + if (self = [super init]) { + _nibName = nibName; + _bundle = bundle; + _configureBlock = [configureBlock copy]; + _sizeBlock = [sizeBlock copy]; + } + return self; +} + #pragma mark - IGListSectionType - (NSInteger)numberOfItems { @@ -48,7 +65,16 @@ - (CGSize)sizeForItemAtIndex:(NSInteger)index { - (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { IGParameterAssert(index == 0); - id cell = [self.collectionContext dequeueReusableCellOfClass:self.cellClass forSectionController:self atIndex:index]; + id cell; + id collectionContext = self.collectionContext; + if ([self.nibName length] > 0) { + cell = [collectionContext dequeueReusableCellWithNibName:self.nibName + bundle:self.bundle + forSectionController:self + atIndex:index]; + } else { + cell = [collectionContext dequeueReusableCellOfClass:self.cellClass forSectionController:self atIndex:index]; + } self.configureBlock(self.item, cell); return cell; } diff --git a/Source/IGListStackedSectionController.m b/Source/IGListStackedSectionController.m index 1b310d996..c3b3a9139 100644 --- a/Source/IGListStackedSectionController.m +++ b/Source/IGListStackedSectionController.m @@ -196,6 +196,18 @@ - (UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass atIndex:(index + offset)]; } +- (UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName + bundle:(NSBundle *)bundle + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index +{ + const NSUInteger offset = [self offsetForSectionController:sectionController]; + return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableCellWithNibName:nibName + bundle:bundle + forSectionController:self + atIndex:(index + offset)]; +} + - (UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind forSectionController:(IGListSectionController *)sectionController class:(Class)viewClass diff --git a/Source/Internal/IGListAdapterInternal.h b/Source/Internal/IGListAdapterInternal.h index 9f1e5701f..d14dca8f2 100644 --- a/Source/Internal/IGListAdapterInternal.h +++ b/Source/Internal/IGListAdapterInternal.h @@ -18,8 +18,8 @@ NS_ASSUME_NONNULL_BEGIN /// Generate a string representation of a reusable view class when registering with a UICollectionView. -NS_INLINE NSString *IGListReusableViewIdentifier(Class viewClass, NSString * _Nullable kind) { - return [NSString stringWithFormat:@"%@%@", kind ?: @"", NSStringFromClass(viewClass)]; +NS_INLINE NSString *IGListReusableViewIdentifier(Class viewClass, NSString * _Nullable nibName, NSString * _Nullable kind) { + return [NSString stringWithFormat:@"%@%@%@", kind ?: @"", nibName ?: @"", NSStringFromClass(viewClass)]; } @interface IGListAdapter () @@ -54,6 +54,7 @@ IGListCollectionContext @property (nonatomic, strong, nullable) IGListSectionMap *previoussectionMap; @property (nonatomic, strong) NSMutableSet *registeredCellClasses; +@property (nonatomic, strong) NSMutableSet *registeredNibNames; @property (nonatomic, strong) NSMutableSet *registeredSupplementaryViewIdentifiers; - (NSArray *)indexPathsFromSectionController:(IGListSectionController *)sectionController diff --git a/Tests/Assets/IGTestNibCell.xib b/Tests/Assets/IGTestNibCell.xib new file mode 100644 index 000000000..40d586347 --- /dev/null +++ b/Tests/Assets/IGTestNibCell.xib @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index 8d16fdcf0..af94e9493 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -143,15 +143,21 @@ - (void)test_whenQueryingIndexPaths_insideBatchUpdateBlock_thatPathsAreEqual { } - (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassName { - NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nil); + NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nil, nil); XCTAssertEqualObjects(identifier, @"UICollectionViewCell"); } - (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassNameAndSupplimentaryKind { - NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, UICollectionElementKindSectionFooter); + NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nil, UICollectionElementKindSectionFooter); XCTAssertEqualObjects(identifier, @"UICollectionElementKindSectionFooterUICollectionViewCell"); } +- (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassNameAndNibName { + NSString *nibName = @"IGNibName"; + NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nibName, nil); + XCTAssertEqualObjects(identifier, @"IGNibNameUICollectionViewCell"); +} + - (void)test_whenDataSourceChanges_thatBackgroundViewVisibilityChanges { self.dataSource.objects = @[@1]; UIView *background = [[UIView alloc] init]; diff --git a/Tests/IGListSingleNibItemControllerTests.m b/Tests/IGListSingleNibItemControllerTests.m new file mode 100644 index 000000000..ebb897f12 --- /dev/null +++ b/Tests/IGListSingleNibItemControllerTests.m @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGTestCell.h" +#import "IGTestSingleNibItemDataSource.h" + +#define genTestObject(k, v) [[IGTestObject alloc] initWithKey:k value:v] + +#define genExpectation [self expectationWithDescription:NSStringFromSelector(_cmd)] + +@interface IGListSingleNibItemControllerTests : XCTestCase + +@property (nonatomic, strong) IGListCollectionView *collectionView; +@property (nonatomic, strong) IGListAdapter *adapter; +@property (nonatomic, strong) IGListAdapterUpdater *updater; +@property (nonatomic, strong) IGTestSingleNibItemDataSource *dataSource; +@property (nonatomic, strong) UIWindow *window; + +@end + +@implementation IGListSingleNibItemControllerTests + +- (void)setUp { + [super setUp]; + self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + self.collectionView = [[IGListCollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layout]; + [self.window addSubview:self.collectionView]; + self.dataSource = [[IGTestSingleNibItemDataSource alloc] init]; + self.updater = [[IGListAdapterUpdater alloc] init]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:self.updater viewController:nil workingRangeSize:2]; +} + +- (void)tearDown { + [super tearDown]; + self.window = nil; + self.collectionView = nil; + self.adapter = nil; + self.dataSource = nil; +} + +- (void)setupWithObjects:(NSArray *)objects { + self.dataSource.objects = objects; + self.adapter.collectionView = self.collectionView; + self.adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; +} + +- (void)test_whenDisplayingCollectionView_thatSectionsHaveOneItem { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 1); +} + +- (void)test_whenDisplayingCollectionView_thatCellsAreConfigured { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + IGTestCell *cell1 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + IGTestCell *cell2 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + IGTestCell *cell3 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]]; + XCTAssertEqualObjects(cell1.label.text, @"Foo"); + XCTAssertEqualObjects(cell2.label.text, @"Bar"); + XCTAssertEqualObjects(cell3.label.text, @"Baz"); +} + +- (void)test_whenDisplayingCollectionView_thatCellsAreSized { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + IGTestCell *cell1 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + IGTestCell *cell2 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + IGTestCell *cell3 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]]; + XCTAssertEqual(cell1.frame.size.height, 44); + XCTAssertEqual(cell2.frame.size.height, 44); + XCTAssertEqual(cell3.frame.size.height, 44); + XCTAssertEqual(cell1.frame.size.width, 100); + XCTAssertEqual(cell2.frame.size.width, 100); + XCTAssertEqual(cell3.frame.size.width, 100); +} + +- (void)test_whenItemUpdated_thatCellIsConfigured { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + self.dataSource.objects = @[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Qux"), // new value + genTestObject(@3, @"Baz"), + ]; + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + IGTestCell *cell2 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + XCTAssertEqualObjects(cell2.label.text, @"Qux"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +@end diff --git a/Tests/Objects/IGTestCell.h b/Tests/Objects/IGTestCell.h index 422aa21ae..690da43e6 100644 --- a/Tests/Objects/IGTestCell.h +++ b/Tests/Objects/IGTestCell.h @@ -12,6 +12,6 @@ @interface IGTestCell : UICollectionViewCell @property (nonatomic, weak) id delegate; -@property (nonatomic, strong) UILabel *label; +@property (nonatomic, strong) IBOutlet UILabel *label; @end diff --git a/Tests/Objects/IGTestSingleItemDataSource.m b/Tests/Objects/IGTestSingleItemDataSource.m index be1eb3455..21e124e47 100644 --- a/Tests/Objects/IGTestSingleItemDataSource.m +++ b/Tests/Objects/IGTestSingleItemDataSource.m @@ -27,8 +27,8 @@ - (NSArray *)objectsForListAdapter:(IGListAdapter *)listAdapter { return CGSizeMake([collectionContext containerSize].width, 44); }; return [[IGListSingleSectionController alloc] initWithCellClass:IGTestCell.class - configureBlock:configureBlock - sizeBlock:sizeBlock]; + configureBlock:configureBlock + sizeBlock:sizeBlock]; } - (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { diff --git a/Tests/Objects/IGTestSingleNibItemDataSource.h b/Tests/Objects/IGTestSingleNibItemDataSource.h new file mode 100644 index 000000000..0c81df282 --- /dev/null +++ b/Tests/Objects/IGTestSingleNibItemDataSource.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +#import "IGTestObject.h" + +@interface IGTestSingleNibItemDataSource : NSObject + +@property (nonatomic, strong) NSArray *objects; + +@end diff --git a/Tests/Objects/IGTestSingleNibItemDataSource.m b/Tests/Objects/IGTestSingleNibItemDataSource.m new file mode 100644 index 000000000..1287d9fba --- /dev/null +++ b/Tests/Objects/IGTestSingleNibItemDataSource.m @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestSingleNibItemDataSource.h" + +#import + +#import "IGTestCell.h" + +@implementation IGTestSingleNibItemDataSource + +- (NSArray> *)objectsForListAdapter:(IGListAdapter *)listAdapter { + return self.objects; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object +{ + void (^configureBlock)(id, __kindof UICollectionViewCell *) = ^(IGTestObject *item, IGTestCell *cell) { + cell.label.text = [item.value description]; + }; + CGSize (^sizeBlock)(id) = ^CGSize(id collectionContext) { + return CGSizeMake([collectionContext containerSize].width, 44); + }; + return [[IGListSingleSectionController alloc] initWithNibName:@"IGTestNibCell" + bundle:[NSBundle bundleForClass:self.class] + configureBlock:configureBlock + sizeBlock:sizeBlock]; +} + +- (UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { + return nil; +} + +@end