diff --git a/CHANGELOG.md b/CHANGELOG.md index 2826570..87d9b91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # CHANGELOG -## 4.0.5 +* Add support for automatic cell registration by conforming to 'AutoRegistering' (table view / collection view). +[@apptekstudios](https://github.com/apptekstudios) +[#63](https://github.com/AliSoftware/Reusable/pull/63) + +## 4.0.5 * Fix for only allowing the use of app extension API. [igorkulman](https://github.com/igorkulman) [#73](https://github.com/AliSoftware/Reusable/pull/73) diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index f0b4dc4..e3687d7 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -56,10 +56,10 @@ /* Begin PBXFileReference section */ 029963D2DD2C3E43DF26B9E6CFC4C77C /* Reusable-tvOS-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "Reusable-tvOS-prefix.pch"; path = "../Reusable-tvOS/Reusable-tvOS-prefix.pch"; sourceTree = ""; }; - 032F07628656348BA344539DF2F3FDB2 /* Reusable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Reusable.framework; path = "Reusable-tvOS.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 032F07628656348BA344539DF2F3FDB2 /* Reusable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Reusable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0447347B9A1D4FB9213447A363F7039C /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 0FBD092B0B485DA6C7B90BE206901D63 /* Pods-ReusableDemo tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ReusableDemo tvOS.release.xcconfig"; sourceTree = ""; }; - 134568E37290F577BF1196CE3781E88A /* Reusable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Reusable.framework; path = "Reusable-iOS.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 134568E37290F577BF1196CE3781E88A /* Reusable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Reusable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1E9CB32CE05A90E5172D6D051DD06C14 /* Pods-ReusableDemo tvOS-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ReusableDemo tvOS-acknowledgements.markdown"; sourceTree = ""; }; 1ED6979FA83E22940E9242F74ECAFF2A /* Reusable-iOS-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Reusable-iOS-dummy.m"; sourceTree = ""; }; 2252BDFE7D9DF619FDD525C082B88752 /* NibOwnerLoadable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NibOwnerLoadable.swift; path = Sources/View/NibOwnerLoadable.swift; sourceTree = ""; }; @@ -75,15 +75,15 @@ 5892AC97FA3ED1A5B9672D91235E50A0 /* Reusable-tvOS-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "Reusable-tvOS-umbrella.h"; path = "../Reusable-tvOS/Reusable-tvOS-umbrella.h"; sourceTree = ""; }; 59FF06E4BEF0F7688B1F724EB17FA572 /* StoryboardBased.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StoryboardBased.swift; path = Sources/Storyboard/StoryboardBased.swift; sourceTree = ""; }; 5AFEACE7A71F4931B930EB6D84C5C91F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - 5B5B35512DA1A50AAFAE6CF87A634BF6 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = ""; }; - 60C791B4753934D6F226F344C203602E /* Pods_ReusableDemo_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_ReusableDemo_iOS.framework; path = "Pods-ReusableDemo iOS.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5B5B35512DA1A50AAFAE6CF87A634BF6 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 60C791B4753934D6F226F344C203602E /* Pods_ReusableDemo_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReusableDemo_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 62D264D4244F785EB7C4672C8F079E34 /* Reusable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Reusable.swift; path = Sources/View/Reusable.swift; sourceTree = ""; }; 651C55EADF4A57B9F079395A27F61ABC /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6C4EA458C8961A4156B23CF70C2F5D1A /* Pods-ReusableDemo tvOS-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-ReusableDemo tvOS-frameworks.sh"; sourceTree = ""; }; 75BE09819782C98834791EFD9D29FF3B /* Pods-ReusableDemo iOS-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ReusableDemo iOS-dummy.m"; sourceTree = ""; }; 8409AF22BF14FCADAFB835B8DA7539F9 /* UICollectionView+Reusable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UICollectionView+Reusable.swift"; path = "Sources/View/UICollectionView+Reusable.swift"; sourceTree = ""; }; - 8CF050962AB868C4567DD94B5A8051C1 /* Reusable.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; path = Reusable.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 8CF050962AB868C4567DD94B5A8051C1 /* Reusable.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; path = Reusable.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 961CF8EA017975C422786939C10D48DB /* Reusable-tvOS.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; name = "Reusable-tvOS.modulemap"; path = "../Reusable-tvOS/Reusable-tvOS.modulemap"; sourceTree = ""; }; 997B29124B8AD227F6B17CE7D11B0953 /* Reusable-iOS.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Reusable-iOS.modulemap"; sourceTree = ""; }; 9E51095A3435FC405B95D01CEB2E6761 /* Reusable-iOS-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Reusable-iOS-umbrella.h"; sourceTree = ""; }; @@ -97,11 +97,11 @@ B8F436E7630A5B236CBEF9145E88B2E2 /* Pods-ReusableDemo iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ReusableDemo iOS.debug.xcconfig"; sourceTree = ""; }; C40FFB95E0E48B6B19EF527718C6E15C /* Pods-ReusableDemo iOS.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-ReusableDemo iOS.modulemap"; sourceTree = ""; }; C7FEEBFF849983E3183C7BFE1C25E73F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - C9504196865F3C990C48E2735785DAAF /* Pods_ReusableDemo_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_ReusableDemo_tvOS.framework; path = "Pods-ReusableDemo tvOS.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + C9504196865F3C990C48E2735785DAAF /* Pods_ReusableDemo_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReusableDemo_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CB686EC9AF111FD8B0570725691219C2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; CFBAD7716370FCF78377CC43259E2DB0 /* Pods-ReusableDemo iOS-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ReusableDemo iOS-umbrella.h"; sourceTree = ""; }; D2E8466DCD52BE65F107BAF60D975F28 /* StoryboardSceneBased.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StoryboardSceneBased.swift; path = Sources/Storyboard/StoryboardSceneBased.swift; sourceTree = ""; }; - D6364B0BC4181B760D63B01FE13EFA0B /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = ""; }; + D6364B0BC4181B760D63B01FE13EFA0B /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D906BF925BD4D3B3E32DC4FBFD28B217 /* Pods-ReusableDemo iOS-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ReusableDemo iOS-acknowledgements.markdown"; sourceTree = ""; }; DC0A06ABC80D9374C09CAC419E67EDB3 /* Pods-ReusableDemo tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ReusableDemo tvOS.debug.xcconfig"; sourceTree = ""; }; ED66A027868D5B876E44283A1D2714D9 /* Pods-ReusableDemo tvOS-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ReusableDemo tvOS-umbrella.h"; sourceTree = ""; }; @@ -431,12 +431,18 @@ attributes = { LastSwiftUpdateCheck = 0930; LastUpgradeCheck = 0930; + TargetAttributes = { + 833A3560B6F3546B84E92E9E7EAB6F13 = { + LastSwiftMigration = 1020; + }; + }; }; buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 7DB346D0F39D3F0E887471402A8071AB; @@ -635,8 +641,7 @@ MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 4.2; SYMROOT = "${SRCROOT}/../build"; TVOS_DEPLOYMENT_TARGET = 9.0; @@ -700,7 +705,7 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -931,7 +936,7 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; diff --git a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Reusable-iOS.xcscheme b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Reusable-iOS.xcscheme index 87f1b87..ead5516 100644 --- a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Reusable-iOS.xcscheme +++ b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Reusable-iOS.xcscheme @@ -1,6 +1,6 @@ - + - + - + diff --git a/Example/ReusableDemo iOS/TableViewCells/MyTableViewHeaderAutoRegistering.swift b/Example/ReusableDemo iOS/TableViewCells/MyTableViewHeaderAutoRegistering.swift new file mode 100755 index 0000000..e3117c5 --- /dev/null +++ b/Example/ReusableDemo iOS/TableViewCells/MyTableViewHeaderAutoRegistering.swift @@ -0,0 +1,32 @@ +// +// MyAutoRegisterHeaderTableView.swift +// ReusableDemo +// +// Created by TJB on 07/08/18. +// Copyright © 2018 ApptekStudios. All rights reserved. +// + +import UIKit +import Reusable + +/** + * This view is loaded from a NIB, and is the XIB file's + * root view (and not the File's Owner). => it is `NibLoadable` + * + * It is also reusable and has a `reuseIdentifier` (as it's a TableViewHeaderFooterView + * and it uses the TableView recycling mechanism) => it is `Reusable` + * + * That's why it's annotated with the `NibReusable` typealias, + * Which in fact is just a convenience typealias that combines + * `NibLoadable` & `Reusable` protocols. + */ +final class MyTableViewHeaderAutoRegistering: UITableViewHeaderFooterView, NibReusable, AutoRegistering { + + @IBOutlet private weak var titleLabel: UILabel! + + static let height: CGFloat = 55 + + func fillForSection(_ section: Int) { + self.titleLabel.text = "Header Section #\(section)" + } +} diff --git a/Example/ReusableDemo iOS/TableViewCells/MyTableViewHeaderAutoRegistering.xib b/Example/ReusableDemo iOS/TableViewCells/MyTableViewHeaderAutoRegistering.xib new file mode 100755 index 0000000..2c25551 --- /dev/null +++ b/Example/ReusableDemo iOS/TableViewCells/MyTableViewHeaderAutoRegistering.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ReusableDemo iOS/TableViewCells/MyXIBInfoCell.swift b/Example/ReusableDemo iOS/TableViewCells/MyXIBInfoCell.swift index 68187d2..ca815b2 100644 --- a/Example/ReusableDemo iOS/TableViewCells/MyXIBInfoCell.swift +++ b/Example/ReusableDemo iOS/TableViewCells/MyXIBInfoCell.swift @@ -20,7 +20,7 @@ import Reusable * Which in fact is just a convenience typealias that combines * `NibLoadable` & `Reusable` protocols. */ -final class MyXIBInfoCell: UITableViewCell, NibReusable { +final class MyXIBInfoCell: UITableViewCell, NibReusable, AutoRegistering { @IBOutlet private weak var titleLabel: UILabel! private var info: String = "" diff --git a/Example/ReusableDemo iOS/TableViewController.swift b/Example/ReusableDemo iOS/TableViewController.swift index 07b8e01..00ae349 100755 --- a/Example/ReusableDemo iOS/TableViewController.swift +++ b/Example/ReusableDemo iOS/TableViewController.swift @@ -16,29 +16,44 @@ final class TableViewController: UITableViewController { tableView.register(cellType: MySimpleColorCell.self) tableView.register(cellType: MyXIBTextCell.self) - tableView.register(cellType: MyXIBInfoCell.self) + + /* Since MyXIBInfoCell is marked as conforming to AutoRegistering, + there's no need to register this type ahead of time */ + // tableView.register(cellType: MyXIBInfoCell.self) /* No need to register this one, the UIStoryboard already auto-register its cells */ -// tableView.registerReusableCell(MyStoryBoardIndexPathCell) + // tableView.registerReusableCell(MyStoryBoardIndexPathCell) } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return MyHeaderTableView.height + switch section { + case 0: + return MyTableViewHeader.height + default: + return MyTableViewHeaderAutoRegistering.height + } } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let frame = CGRect( - x: 0, - y: 0, - width: tableView.bounds.size.width, - height: self.tableView(tableView, heightForHeaderInSection: section) - ) - // See the overridden `MyHeaderTableView.init(frame:)` initializer, which - // automatically loads the view content from its nib using loadNibContent() - let view = MyHeaderTableView(frame: frame) - - view.fillForSection(section) - return view + switch section { + case 0: + let frame = CGRect( + x: 0, + y: 0, + width: tableView.bounds.size.width, + height: self.tableView(tableView, heightForHeaderInSection: section) + ) + // See the overridden `MyHeaderTableView.init(frame:)` initializer, which + // automatically loads the view content from its nib using loadNibContent() + let view = MyTableViewHeader(frame: frame) + view.fillForSection(section) + return view + default: + // This header class is set to auto-register itself + let view: MyTableViewHeaderAutoRegistering = tableView.dequeueReusableHeaderFooterView() + view.fillForSection(section) + return view + } } override func numberOfSections(in tableView: UITableView) -> Int { @@ -61,6 +76,7 @@ final class TableViewController: UITableViewController { textCell.fill("{section \(indexPath.section), row \(indexPath.row)}") return textCell case 2: + // Note that auto-register is enabled here let infoCell = tableView.dequeueReusableCell(for: indexPath) as MyXIBInfoCell infoCell.fill("InfoCell #\(indexPath.row)", info: "Info #\(indexPath.row)", details: "Details #\(indexPath.row)") return infoCell diff --git a/Example/ReusableDemo.xcodeproj/project.pbxproj b/Example/ReusableDemo.xcodeproj/project.pbxproj index ca93b35..902751c 100644 --- a/Example/ReusableDemo.xcodeproj/project.pbxproj +++ b/Example/ReusableDemo.xcodeproj/project.pbxproj @@ -34,11 +34,13 @@ 37E3FC761E55CDD8000A7436 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37E3FC751E55CDD8000A7436 /* Assets.xcassets */; }; 37E3FC7E1E55D056000A7436 /* MyCustomWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E3FC7D1E55D056000A7436 /* MyCustomWidget.swift */; }; 37E3FC801E55D08C000A7436 /* MyCustomWidget.xib in Resources */ = {isa = PBXBuildFile; fileRef = 37E3FC7F1E55D08C000A7436 /* MyCustomWidget.xib */; }; - 3BC2191F1CFC259F003BE78C /* MyHeaderTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC2191D1CFC259F003BE78C /* MyHeaderTableView.swift */; }; - 3BC219201CFC259F003BE78C /* MyHeaderTableView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3BC2191E1CFC259F003BE78C /* MyHeaderTableView.xib */; }; 47EE563C1D66155100AD3E4D /* MyCustomWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EE563B1D66155100AD3E4D /* MyCustomWidget.swift */; }; 47EE563E1D66155D00AD3E4D /* MyCustomWidget.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47EE563D1D66155D00AD3E4D /* MyCustomWidget.xib */; }; 9FA94C08BEF301B3E3BDC872 /* Pods_ReusableDemo_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F2D22CAD76679D19CD171C1 /* Pods_ReusableDemo_tvOS.framework */; }; + B8CF34C82173864C0066D6BF /* MyTableViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CF34C62173864C0066D6BF /* MyTableViewHeader.swift */; }; + B8CF34C92173864C0066D6BF /* MyTableViewHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = B8CF34C72173864C0066D6BF /* MyTableViewHeader.xib */; }; + B8CF34CC217386680066D6BF /* MyTableViewHeaderAutoRegistering.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CF34CA217386680066D6BF /* MyTableViewHeaderAutoRegistering.swift */; }; + B8CF34CD217386680066D6BF /* MyTableViewHeaderAutoRegistering.xib in Resources */ = {isa = PBXBuildFile; fileRef = B8CF34CB217386680066D6BF /* MyTableViewHeaderAutoRegistering.xib */; }; E5B86DD32EF6226743A8E042 /* Pods_ReusableDemo_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FF21AA4AB6E6CEF7985FF09B /* Pods_ReusableDemo_iOS.framework */; }; /* End PBXBuildFile section */ @@ -75,13 +77,15 @@ 37E3FC771E55CDD8000A7436 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37E3FC7D1E55D056000A7436 /* MyCustomWidget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyCustomWidget.swift; sourceTree = ""; }; 37E3FC7F1E55D08C000A7436 /* MyCustomWidget.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MyCustomWidget.xib; sourceTree = ""; }; - 3BC2191D1CFC259F003BE78C /* MyHeaderTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyHeaderTableView.swift; sourceTree = ""; }; - 3BC2191E1CFC259F003BE78C /* MyHeaderTableView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MyHeaderTableView.xib; sourceTree = ""; }; 4033B8B5AA57F6CB717FCF24 /* Pods-ReusableDemo iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReusableDemo iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReusableDemo iOS/Pods-ReusableDemo iOS.release.xcconfig"; sourceTree = ""; }; 47EE563B1D66155100AD3E4D /* MyCustomWidget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyCustomWidget.swift; sourceTree = ""; }; 47EE563D1D66155D00AD3E4D /* MyCustomWidget.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MyCustomWidget.xib; sourceTree = ""; }; 4F2D22CAD76679D19CD171C1 /* Pods_ReusableDemo_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReusableDemo_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4F85C1AE1D6502CAE2A6E6AC /* Pods-ReusableDemo tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReusableDemo tvOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReusableDemo tvOS/Pods-ReusableDemo tvOS.debug.xcconfig"; sourceTree = ""; }; + B8CF34C62173864C0066D6BF /* MyTableViewHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyTableViewHeader.swift; sourceTree = ""; }; + B8CF34C72173864C0066D6BF /* MyTableViewHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MyTableViewHeader.xib; sourceTree = ""; }; + B8CF34CA217386680066D6BF /* MyTableViewHeaderAutoRegistering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyTableViewHeaderAutoRegistering.swift; sourceTree = ""; }; + B8CF34CB217386680066D6BF /* MyTableViewHeaderAutoRegistering.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MyTableViewHeaderAutoRegistering.xib; sourceTree = ""; }; D8F45C0C4C06E7B1B16AC822 /* Pods-ReusableDemo tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReusableDemo tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReusableDemo tvOS/Pods-ReusableDemo tvOS.release.xcconfig"; sourceTree = ""; }; FF21AA4AB6E6CEF7985FF09B /* Pods_ReusableDemo_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReusableDemo_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -168,8 +172,10 @@ 09B3461F1C4ED34F00BA041F /* TableView Cells */ = { isa = PBXGroup; children = ( - 3BC2191D1CFC259F003BE78C /* MyHeaderTableView.swift */, - 3BC2191E1CFC259F003BE78C /* MyHeaderTableView.xib */, + B8CF34C62173864C0066D6BF /* MyTableViewHeader.swift */, + B8CF34C72173864C0066D6BF /* MyTableViewHeader.xib */, + B8CF34CA217386680066D6BF /* MyTableViewHeaderAutoRegistering.swift */, + B8CF34CB217386680066D6BF /* MyTableViewHeaderAutoRegistering.xib */, 09B346201C4ED39700BA041F /* MyXIBTextCell.swift */, 09B346211C4ED39700BA041F /* MyXIBTextCell.xib */, 09B346261C4ED51600BA041F /* MyXIBInfoCell.swift */, @@ -294,7 +300,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = AliSoftware; TargetAttributes = { 09B346081C4ED26F00BA041F = { @@ -311,7 +317,7 @@ }; buildConfigurationList = 09B346041C4ED26F00BA041F /* Build configuration list for PBXProject "ReusableDemo" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -335,13 +341,14 @@ files = ( 09B346231C4ED39700BA041F /* MyXIBTextCell.xib in Resources */, 09B346461C4EF8F600BA041F /* CollectionHeaderView.xib in Resources */, + B8CF34CD217386680066D6BF /* MyTableViewHeaderAutoRegistering.xib in Resources */, 09B346171C4ED26F00BA041F /* LaunchScreen.storyboard in Resources */, 47EE563E1D66155D00AD3E4D /* MyCustomWidget.xib in Resources */, 09441AE21D32A0390029B8A6 /* InfoViewController.storyboard in Resources */, 09B346141C4ED26F00BA041F /* Assets.xcassets in Resources */, 09B346251C4ED4CE00BA041F /* Default-568h@2x.png in Resources */, 09B346121C4ED26F00BA041F /* Main.storyboard in Resources */, - 3BC219201CFC259F003BE78C /* MyHeaderTableView.xib in Resources */, + B8CF34C92173864C0066D6BF /* MyTableViewHeader.xib in Resources */, 09B346421C4EF71900BA041F /* MyXIBIndexSquaceCell.xib in Resources */, 09B346291C4ED51600BA041F /* MyXIBInfoCell.xib in Resources */, ); @@ -474,16 +481,17 @@ 09B346411C4EF71900BA041F /* MyXIBIndexSquaceCell.swift in Sources */, 09B346341C4EDC7D00BA041F /* MyStoryBoardIndexPathCell.swift in Sources */, 09B346221C4ED39700BA041F /* MyXIBTextCell.swift in Sources */, + B8CF34C82173864C0066D6BF /* MyTableViewHeader.swift in Sources */, 47EE563C1D66155100AD3E4D /* MyCustomWidget.swift in Sources */, 09B3463C1C4EF46100BA041F /* MyStoryboardTextSquareCell.swift in Sources */, 09B3463E1C4EF4A400BA041F /* CollectionViewController.swift in Sources */, 09B3460F1C4ED26F00BA041F /* TableViewController.swift in Sources */, 09441AE41D32A0700029B8A6 /* InfoDetailViewController.swift in Sources */, 09B3460D1C4ED26F00BA041F /* AppDelegate.swift in Sources */, - 3BC2191F1CFC259F003BE78C /* MyHeaderTableView.swift in Sources */, 09441AE61D32A07D0029B8A6 /* InfoViewController.swift in Sources */, 09B346281C4ED51600BA041F /* MyXIBInfoCell.swift in Sources */, 09B346441C4EF8B700BA041F /* CollectionHeaderView.swift in Sources */, + B8CF34CC217386680066D6BF /* MyTableViewHeaderAutoRegistering.swift in Sources */, 09B3462B1C4ED6C400BA041F /* MySimpleColorCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -532,6 +540,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -588,6 +597,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; diff --git a/Example/ReusableDemo.xcodeproj/xcshareddata/xcschemes/ReusableDemo iOS.xcscheme b/Example/ReusableDemo.xcodeproj/xcshareddata/xcschemes/ReusableDemo iOS.xcscheme index 6b7f770..f9f133f 100644 --- a/Example/ReusableDemo.xcodeproj/xcshareddata/xcschemes/ReusableDemo iOS.xcscheme +++ b/Example/ReusableDemo.xcodeproj/xcshareddata/xcschemes/ReusableDemo iOS.xcscheme @@ -1,6 +1,6 @@ -## 2. Register your cells +## 2. Either enable AutoRegistering or manually register your cells -Unless you've prototyped your cell in a Storyboard, you'll have to register the cell class or Nib by code. +In order to have your cells automatically registered to a TableView/CollectionView when they are first used, simply add conformance to the `AutoRegistering` protocol. -To do this, instead of calling `registerClass(…)` or `registerNib(…)` using a String-based `reuseIdentifier`, just call: +```swift +final class CustomCell: UITableViewCell, Reusable, AutoRegistering { } +``` + +If you've prototyped your cell in a Storyboard, there is no need to conform to 'AutoRegistering' or manually register, as this is done automatically by the Storyboard. + + +Alternatively you will have to register the cell class or Nib by code. To do this, just call: ```swift tableView.register(cellType: theCellClass.self) diff --git a/Reusable.podspec b/Reusable.podspec index bd2cd28..0563c77 100644 --- a/Reusable.podspec +++ b/Reusable.podspec @@ -32,7 +32,7 @@ Pod::Spec.new do |s| s.tvos.deployment_target = "9.0" s.source = { :git => "https://github.com/AliSoftware/Reusable.git", :tag => s.version.to_s } - s.swift_version = '4.2' + s.swift_version = '5.0' s.subspec 'View' do |ss| ss.source_files = "Sources/View/*.swift" diff --git a/Sources/View/NibOwnerLoadable.swift b/Sources/View/NibOwnerLoadable.swift index 3cd1711..fa8480b 100644 --- a/Sources/View/NibOwnerLoadable.swift +++ b/Sources/View/NibOwnerLoadable.swift @@ -36,9 +36,12 @@ public extension NibOwnerLoadable where Self: UIView { /** Adds content loaded from the nib to the end of the receiver's list of subviews and adds constraints automatically. */ - func loadNibContent() { + @discardableResult + func loadNibContent() -> UIView? { let layoutAttributes: [NSLayoutConstraint.Attribute] = [.top, .leading, .bottom, .trailing] + var firstView: UIView? for case let view as UIView in Self.nib.instantiate(withOwner: self, options: nil) { + if firstView == nil { firstView = view } view.translatesAutoresizingMaskIntoConstraints = false self.addSubview(view) NSLayoutConstraint.activate(layoutAttributes.map { attribute in @@ -50,6 +53,7 @@ public extension NibOwnerLoadable where Self: UIView { ) }) } + return firstView } } diff --git a/Sources/View/Reusable.swift b/Sources/View/Reusable.swift index 28f8e01..6de7f36 100644 --- a/Sources/View/Reusable.swift +++ b/Sources/View/Reusable.swift @@ -23,6 +23,13 @@ public protocol Reusable: class { /// to be able to dequeue them in a type-safe manner public typealias NibReusable = Reusable & NibLoadable +/// Make your `UITableViewCell`, `UITableViewHeaderFooterView`, and 'UICollectionViewCell' +/// subclasses conform to this protocol when you want to avoid having to call +/// register(cellType:) explicitly on your cell classes before being able +/// to dequeue them. Cell types conforming to this will auto-register the +/// cell if not registered already when you try to dequeue one for the first time. +public protocol AutoRegistering {} + // MARK: - Default implementation public extension Reusable { diff --git a/Sources/View/UICollectionView+Reusable.swift b/Sources/View/UICollectionView+Reusable.swift index 318ce38..b30de8a 100644 --- a/Sources/View/UICollectionView+Reusable.swift +++ b/Sources/View/UICollectionView+Reusable.swift @@ -11,6 +11,32 @@ import UIKit // MARK: Reusable support for UICollectionView public extension UICollectionView { + /** + Register a NIB-Based `UICollectionViewCell` subclass (conforming to `Reusable` & `NibLoadable`) + + - parameter cellType: the `UICollectionViewCell` (`Reusable` & `NibLoadable`-conforming) subclass to register + + - seealso: `register(_:,forCellWithReuseIdentifier:)` + */ + final func register(cellType: T.Type) + where T: NibReusable & AutoRegistering { + self.register(cellType.nib, forCellWithReuseIdentifier: cellType.reuseIdentifier) + setHasRegistered(cellType: cellType) + } + + /** + Register a NIB-Based `UICollectionViewCell` subclass (conforming to `Reusable` & `NibLoadable`) + + - parameter cellType: the `UICollectionViewCell` (`Reusable` & `NibLoadable`-conforming) subclass to register + + - seealso: `register(_:,forCellWithReuseIdentifier:)` + */ + final func register(cellType: T.Type) + where T: Reusable & AutoRegistering { + self.register(cellType.self, forCellWithReuseIdentifier: cellType.reuseIdentifier) + setHasRegistered(cellType: cellType) + } + /** Register a NIB-Based `UICollectionViewCell` subclass (conforming to `Reusable` & `NibLoadable`) @@ -19,7 +45,7 @@ public extension UICollectionView { - seealso: `register(_:,forCellWithReuseIdentifier:)` */ final func register(cellType: T.Type) - where T: Reusable & NibLoadable { + where T: NibReusable { self.register(cellType.nib, forCellWithReuseIdentifier: cellType.reuseIdentifier) } @@ -37,24 +63,70 @@ public extension UICollectionView { /** Returns a reusable `UICollectionViewCell` object for the class inferred by the return-type - + - parameter indexPath: The index path specifying the location of the cell. - parameter cellType: The cell class to dequeue + + - returns: A `Reusable`, `UICollectionViewCell` instance + + - note: The `cellType` parameter can generally be omitted and infered by the return type, + except when your type is in a variable and cannot be determined at compile time. + - seealso: `dequeueReusableCell(withReuseIdentifier:,for:)` + */ + final func dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) -> T + where T: Reusable & AutoRegistering { + if !hasRegistered(cellType: cellType) { + register(cellType: cellType) + } + return dequeueReusableCellInternal(for: indexPath) + } + /** + Returns a reusable `UICollectionViewCell` object for the class inferred by the return-type + + - parameter indexPath: The index path specifying the location of the cell. + - parameter cellType: The cell class to dequeue + - returns: A `Reusable`, `UICollectionViewCell` instance + + - note: The `cellType` parameter can generally be omitted and infered by the return type, + except when your type is in a variable and cannot be determined at compile time. + - seealso: `dequeueReusableCell(withReuseIdentifier:,for:)` + */ + final func dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) -> T + where T: NibReusable & AutoRegistering { + if !hasRegistered(cellType: cellType) { + register(cellType: cellType) + } + return dequeueReusableCellInternal(for: indexPath) + } + /** + Returns a reusable `UICollectionViewCell` object for the class inferred by the return-type + + - parameter indexPath: The index path specifying the location of the cell. + - parameter cellType: The cell class to dequeue + + - returns: A `Reusable`, `UICollectionViewCell` instance + - note: The `cellType` parameter can generally be omitted and infered by the return type, except when your type is in a variable and cannot be determined at compile time. - seealso: `dequeueReusableCell(withReuseIdentifier:,for:)` */ final func dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) -> T + where T: Reusable { + return dequeueReusableCellInternal(for: indexPath) + } + + final private func dequeueReusableCellInternal(for indexPath: IndexPath, + cellType: T.Type = T.self) -> T where T: Reusable { let bareCell = self.dequeueReusableCell(withReuseIdentifier: cellType.reuseIdentifier, for: indexPath) guard let cell = bareCell as? T else { fatalError( "Failed to dequeue a cell with identifier \(cellType.reuseIdentifier) matching type \(cellType.self). " + "Check that the reuseIdentifier is set properly in your XIB/Storyboard " - + "and that you registered the cell beforehand" + + "and that you registered the cell beforehand or conformed to AutoRegistering" ) } return cell @@ -63,11 +135,51 @@ public extension UICollectionView { /** Register a NIB-Based `UICollectionReusableView` subclass (conforming to `Reusable` & `NibLoadable`) as a Supplementary View + + - parameter supplementaryViewType: the `UIView` (`Reusable` & `NibLoadable`-conforming) subclass + to register as Supplementary View + - parameter elementKind: The kind of supplementary view to create. + + - seealso: `register(_:,forSupplementaryViewOfKind:,withReuseIdentifier:)` + */ + final func register(supplementaryViewType: T.Type, ofKind elementKind: String) + where T: NibReusable & AutoRegistering { + self.register( + supplementaryViewType.nib, + forSupplementaryViewOfKind: elementKind, + withReuseIdentifier: supplementaryViewType.reuseIdentifier + ) + setHasRegistered(supplementaryViewType: supplementaryViewType, ofKind: elementKind) + } + /** + Register a NIB-Based `UICollectionReusableView` subclass (conforming to `Reusable` & `NibLoadable`) + as a Supplementary View + - parameter supplementaryViewType: the `UIView` (`Reusable` & `NibLoadable`-conforming) subclass to register as Supplementary View - parameter elementKind: The kind of supplementary view to create. + + - seealso: `register(_:,forSupplementaryViewOfKind:,withReuseIdentifier:)` + */ + final func register(supplementaryViewType: T.Type, ofKind elementKind: String) + where T: Reusable & AutoRegistering { + self.register( + supplementaryViewType.self, + forSupplementaryViewOfKind: elementKind, + withReuseIdentifier: supplementaryViewType.reuseIdentifier + ) + setHasRegistered(supplementaryViewType: supplementaryViewType, ofKind: elementKind) + } + /** + Register a NIB-Based `UICollectionReusableView` subclass (conforming to `Reusable` & `NibLoadable`) + as a Supplementary View + + - parameter supplementaryViewType: the `UIView` (`Reusable` & `NibLoadable`-conforming) subclass + to register as Supplementary View + - parameter elementKind: The kind of supplementary view to create. + - seealso: `register(_:,forSupplementaryViewOfKind:,withReuseIdentifier:)` */ final func register(supplementaryViewType: T.Type, ofKind elementKind: String) @@ -96,6 +208,50 @@ public extension UICollectionView { ) } + /** + Returns a reusable `UICollectionReusableView` object for the class inferred by the return-type + + - parameter elementKind: The kind of supplementary view to retrieve. + - parameter indexPath: The index path specifying the location of the cell. + - parameter viewType: The view class to dequeue + + - returns: A `Reusable`, `UICollectionReusableView` instance + + - note: The `viewType` parameter can generally be omitted and infered by the return type, + except when your type is in a variable and cannot be determined at compile time. + - seealso: `dequeueReusableSupplementaryView(ofKind:,withReuseIdentifier:,for:)` + */ + final func dequeueReusableSupplementaryView + (ofKind elementKind: String, for indexPath: IndexPath, viewType: T.Type = T.self) -> T + where T: Reusable & AutoRegistering { + if !hasRegistered(supplementaryViewType: viewType, ofKind: elementKind) { + register(supplementaryViewType: viewType, ofKind: elementKind) + } + return dequeueReusableSupplementaryViewInternal(ofKind: elementKind, for: indexPath) + } + + /** + Returns a reusable `UICollectionReusableView` object for the class inferred by the return-type + + - parameter elementKind: The kind of supplementary view to retrieve. + - parameter indexPath: The index path specifying the location of the cell. + - parameter viewType: The view class to dequeue + + - returns: A `Reusable`, `UICollectionReusableView` instance + + - note: The `viewType` parameter can generally be omitted and infered by the return type, + except when your type is in a variable and cannot be determined at compile time. + - seealso: `dequeueReusableSupplementaryView(ofKind:,withReuseIdentifier:,for:)` + */ + final func dequeueReusableSupplementaryView + (ofKind elementKind: String, for indexPath: IndexPath, viewType: T.Type = T.self) -> T + where T: NibReusable & AutoRegistering { + if !hasRegistered(supplementaryViewType: viewType, ofKind: elementKind) { + register(supplementaryViewType: viewType, ofKind: elementKind) + } + return dequeueReusableSupplementaryViewInternal(ofKind: elementKind, for: indexPath) + } + /** Returns a reusable `UICollectionReusableView` object for the class inferred by the return-type @@ -110,6 +266,12 @@ public extension UICollectionView { - seealso: `dequeueReusableSupplementaryView(ofKind:,withReuseIdentifier:,for:)` */ final func dequeueReusableSupplementaryView + (ofKind elementKind: String, for indexPath: IndexPath, viewType: T.Type = T.self) -> T + where T: Reusable { + return dequeueReusableSupplementaryViewInternal(ofKind: elementKind, for: indexPath) + } + + final private func dequeueReusableSupplementaryViewInternal (ofKind elementKind: String, for indexPath: IndexPath, viewType: T.Type = T.self) -> T where T: Reusable { let view = self.dequeueReusableSupplementaryView( @@ -122,9 +284,46 @@ public extension UICollectionView { "Failed to dequeue a supplementary view with identifier \(viewType.reuseIdentifier) " + "matching type \(viewType.self). " + "Check that the reuseIdentifier is set properly in your XIB/Storyboard " - + "and that you registered the supplementary view beforehand" + + "and that you registered the supplementary view beforehand or conformed to AutoRegistering" ) } return typedView } } + +private var registeredCellTypesKey: Void? + +private extension UICollectionView { + private class RegisteredCellTypes { + var cellReuseIdentifiers: Set = [] + var supplementaryViewReuseIdentifiers: [String: String] = [:] //[ElementKind: ReuseIdentifier] + } + + private var registeredCellTypes: RegisteredCellTypes { + if let obj = (objc_getAssociatedObject(self, ®isteredCellTypesKey) as? RegisteredCellTypes) { + return obj + } else { + let obj = RegisteredCellTypes() + objc_setAssociatedObject(self, ®isteredCellTypesKey, obj, .OBJC_ASSOCIATION_RETAIN) //Create assoc. object + return obj + } + } + + final private func hasRegistered(cellType type: T.Type) -> Bool where T: Reusable { + return registeredCellTypes.cellReuseIdentifiers.contains(type.reuseIdentifier) + } + + final private func setHasRegistered(cellType type: T.Type) where T: Reusable { + registeredCellTypes.cellReuseIdentifiers.insert(type.reuseIdentifier) + } + + final private func hasRegistered(supplementaryViewType type: T.Type, + ofKind elementKind: String) -> Bool where T: Reusable { + return registeredCellTypes.supplementaryViewReuseIdentifiers[elementKind] == type.reuseIdentifier + } + + final private func setHasRegistered(supplementaryViewType type: T.Type, + ofKind elementKind: String) where T: Reusable { + registeredCellTypes.supplementaryViewReuseIdentifiers[elementKind] = type.reuseIdentifier + } +} diff --git a/Sources/View/UITableView+Reusable.swift b/Sources/View/UITableView+Reusable.swift index fc36044..1250e37 100644 --- a/Sources/View/UITableView+Reusable.swift +++ b/Sources/View/UITableView+Reusable.swift @@ -19,7 +19,7 @@ public extension UITableView { - seealso: `register(_:,forCellReuseIdentifier:)` */ final func register(cellType: T.Type) - where T: Reusable & NibLoadable { + where T: NibReusable { self.register(cellType.nib, forCellReuseIdentifier: cellType.reuseIdentifier) } @@ -47,13 +47,107 @@ public extension UITableView { except when your type is in a variable and cannot be determined at compile time. - seealso: `dequeueReusableCell(withIdentifier:,for:)` */ - final func dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) -> T + final func dequeueReusableCell( + for indexPath: IndexPath, + cellType: T.Type = T.self) -> T + where T: Reusable & AutoRegistering { + return dequeueReusableCell( + for: indexPath, + cellType: cellType, + // use the `register(cellType:)` implementation from Reusable conformance + registerClosure: { self.register(cellType: cellType) } + ) + } + + /** + Returns a reusable `UITableViewCell` object for the class inferred by the return-type + + - parameter indexPath: The index path specifying the location of the cell. + - parameter cellType: The cell class to dequeue + + - returns: A `Reusable`, `UITableViewCell` instance + + - note: The `cellType` parameter can generally be omitted and infered by the return type, + except when your type is in a variable and cannot be determined at compile time. + - seealso: `dequeueReusableCell(withIdentifier:,for:)` + */ + final func dequeueReusableCell( + for indexPath: IndexPath, + cellType: T.Type = T.self) -> T + where T: NibReusable & AutoRegistering { + return dequeueReusableCell( + for: indexPath, + cellType: cellType, + // use the `register(cellType:)` implementation from NibReusable conformance + registerClosure: { self.register(cellType: cellType) } + ) + } + + /** + Returns a reusable `UITableViewCell` object for the class inferred by the return-type + + - parameter indexPath: The index path specifying the location of the cell. + - parameter cellType: The cell class to dequeue + + - returns: A `Reusable`, `UITableViewCell` instance + + - note: The `cellType` parameter can generally be omitted and infered by the return type, + except when your type is in a variable and cannot be determined at compile time. + - seealso: `dequeueReusableCell(withIdentifier:,for:)` + */ + final func dequeueReusableCell( + for indexPath: IndexPath, + cellType: T.Type = T.self) -> T where T: Reusable { - guard let cell = self.dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else { + return dequeueReusableCell( + for: indexPath, + cellType: cellType, + registerClosure: nil + ) + } + + /** + Returns a reusable `UITableViewCell` object for the class inferred by the return-type + + - parameter indexPath: The index path specifying the location of the cell. + - parameter cellType: The cell class to dequeue + + - returns: A `Reusable`, `UITableViewCell` instance + + - note: The `cellType` parameter can generally be omitted and infered by the return type, + except when your type is in a variable and cannot be determined at compile time. + - seealso: `dequeueReusableCell(withIdentifier:,for:)` + */ + final func dequeueReusableCell( + for indexPath: IndexPath, + cellType: T.Type = T.self) -> T + where T: NibReusable { + return dequeueReusableCell( + for: indexPath, + cellType: cellType, + registerClosure: nil + ) + } + + private func dequeueReusableCell( + for indexPath: IndexPath, + cellType: T.Type = T.self, + registerClosure: (() -> Void)?) -> T + where T: Reusable { + if let registerClosure = registerClosure { + if let bareCell = self.dequeueReusableCell(withIdentifier: cellType.reuseIdentifier) as? T { + return bareCell // Get a cell if we can + } else { + registerClosure() // Register class or nib + } + } + let bareCell = self.dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath) as? T + guard let cell = bareCell else { fatalError( - "Failed to dequeue a cell with identifier \(cellType.reuseIdentifier) matching type \(cellType.self). " + "Failed to dequeue a cell with identifier \(cellType.reuseIdentifier)" + + "matching type \(cellType.self). " + "Check that the reuseIdentifier is set properly in your XIB/Storyboard " - + "and that you registered the cell beforehand" + + "and that you registered the cell beforehand or conformed to AutoRegistering" ) } return cell @@ -68,7 +162,7 @@ public extension UITableView { - seealso: `register(_:,forHeaderFooterViewReuseIdentifier:)` */ final func register(headerFooterViewType: T.Type) - where T: Reusable & NibLoadable { + where T: NibReusable { self.register(headerFooterViewType.nib, forHeaderFooterViewReuseIdentifier: headerFooterViewType.reuseIdentifier) } @@ -95,14 +189,48 @@ public extension UITableView { except when your type is in a variable and cannot be determined at compile time. - seealso: `dequeueReusableHeaderFooterView(withIdentifier:)` */ - final func dequeueReusableHeaderFooterView(_ viewType: T.Type = T.self) -> T? + final func dequeueReusableHeaderFooterView(_ viewType: T.Type = T.self) -> T + where T: Reusable & AutoRegistering { + return dequeueReusableHeaderFooterView( + registerClosure: { self.register(headerFooterViewType: viewType) } + ) + } + + /** + Returns a reusable `UITableViewHeaderFooterView` object for the class inferred by the return-type + + - parameter viewType: The view class to dequeue + + - returns: A `Reusable`, `UITableViewHeaderFooterView` instance + + - note: The `viewType` parameter can generally be omitted and infered by the return type, + except when your type is in a variable and cannot be determined at compile time. + - seealso: `dequeueReusableHeaderFooterView(withIdentifier:)` + */ + final func dequeueReusableHeaderFooterView(_ viewType: T.Type = T.self) -> T + where T: NibReusable & AutoRegistering { + return dequeueReusableHeaderFooterView( + registerClosure: { self.register(headerFooterViewType: viewType) } + ) + } + + private + final func dequeueReusableHeaderFooterView(_ viewType: T.Type = T.self, + registerClosure: (() -> Void)?) -> T where T: Reusable { - guard let view = self.dequeueReusableHeaderFooterView(withIdentifier: viewType.reuseIdentifier) as? T? else { + if let registerClosure = registerClosure { + if let view = self.dequeueReusableHeaderFooterView(withIdentifier: viewType.reuseIdentifier) as? T { + return view // Get a cell if we can + } else { + registerClosure() // Register class or nib + } + } + guard let view = self.dequeueReusableHeaderFooterView(withIdentifier: viewType.reuseIdentifier) as? T else { fatalError( "Failed to dequeue a header/footer with identifier \(viewType.reuseIdentifier) " + "matching type \(viewType.self). " + "Check that the reuseIdentifier is set properly in your XIB/Storyboard " - + "and that you registered the header/footer beforehand" + + "and that you registered the header/footer beforehand or conformed to AutoRegistering" ) } return view