From 93adf5d3b719475f16cfe9b5321df7974513fc2a Mon Sep 17 00:00:00 2001 From: Ryo Aoyama Date: Tue, 3 Sep 2019 03:42:02 +0900 Subject: [PATCH 01/21] Introduce declarative syntax using function builder --- Carbon.xcodeproj/project.pbxproj | 40 +++++ .../Sources/Common/FooterContent.xib | 13 +- .../Sources/Common/HeaderContent.xib | 15 +- .../Example-iOS/Sources/Common/Spacing.swift | 2 +- .../Sources/Emoji/EmojiViewController.swift | 25 +--- .../Sources/Emoji/EmojiViewController.xib | 15 +- .../Sources/Form/FormViewController.swift | 137 ++++++++---------- .../Sources/Form/FormViewController.xib | 11 +- .../Sources/Hello/HelloMessageContent.xib | 17 +-- .../Sources/Hello/HelloViewController.swift | 40 ++--- .../Sources/Hello/HelloViewController.xib | 12 +- .../Sources/Kyoto/KyotoLicenseContent.xib | 15 +- .../Example-iOS/Sources/Kyoto/KyotoTop.swift | 2 +- .../Sources/Kyoto/KyotoViewController.swift | 42 +++--- .../Sources/Kyoto/KyotoViewController.xib | 11 +- .../Sources/Pangram/PangramLabel.swift | 2 +- .../Pangram/PangramViewController.swift | 27 ++-- .../Sources/Pangram/PangramViewController.xib | 13 +- .../Example-iOS/Sources/Todo/TodoEmpty.swift | 2 +- .../Sources/Todo/TodoEmptyContent.xib | 13 +- .../Sources/Todo/TodoTextContent.xib | 11 +- .../Sources/Todo/TodoViewController.swift | 77 +++++----- .../Sources/Todo/TodoViewController.xib | 15 +- .../Sources/Top/HomeItemContent.xib | 13 +- .../Sources/Top/HomeViewController.swift | 62 ++++---- .../Sources/Top/HomeViewController.xib | 12 +- Sources/CellNode.swift | 6 + Sources/Component.swift | 10 ++ Sources/FunctionBuilder/CellGroup.swift | 19 +++ Sources/FunctionBuilder/CellsBuildable.swift | 9 ++ Sources/FunctionBuilder/CellsBuilder.swift | 131 +++++++++++++++++ .../FunctionBuilder/ComponentWrapping.swift | 45 ++++++ .../IdentifiedComponentWrapper.swift | 9 ++ Sources/FunctionBuilder/SectionGroup.swift | 19 +++ .../FunctionBuilder/SectionsBuildable.swift | 9 ++ Sources/FunctionBuilder/SectionsBuilder.swift | 131 +++++++++++++++++ Sources/IdentifiableComponent.swift | 8 +- Sources/Renderer.swift | 15 ++ Sources/Section.swift | 60 ++++++++ 39 files changed, 791 insertions(+), 324 deletions(-) create mode 100644 Sources/FunctionBuilder/CellGroup.swift create mode 100644 Sources/FunctionBuilder/CellsBuildable.swift create mode 100644 Sources/FunctionBuilder/CellsBuilder.swift create mode 100644 Sources/FunctionBuilder/ComponentWrapping.swift create mode 100644 Sources/FunctionBuilder/IdentifiedComponentWrapper.swift create mode 100644 Sources/FunctionBuilder/SectionGroup.swift create mode 100644 Sources/FunctionBuilder/SectionsBuildable.swift create mode 100644 Sources/FunctionBuilder/SectionsBuilder.swift diff --git a/Carbon.xcodeproj/project.pbxproj b/Carbon.xcodeproj/project.pbxproj index fe18847..60947d4 100644 --- a/Carbon.xcodeproj/project.pbxproj +++ b/Carbon.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 6B1BE6B722E5CA0D0054DB46 /* ComponentRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1BE6B622E5CA0D0054DB46 /* ComponentRenderable.swift */; }; + 6B37B2F52320339300A80D62 /* IdentifiedComponentWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B37B2F32320339300A80D62 /* IdentifiedComponentWrapper.swift */; }; + 6B37B2F62320339300A80D62 /* ComponentWrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B37B2F42320339300A80D62 /* ComponentWrapping.swift */; }; 6B5304532201EDB200A3E21E /* DataChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5304522201EDB200A3E21E /* DataChangeset.swift */; }; 6B6594A421E2532100291AAF /* IdentifiableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B65948621E2532100291AAF /* IdentifiableComponent.swift */; }; 6B6594A621E2532100291AAF /* UICollectionViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B65948A21E2532100291AAF /* UICollectionViewAdapter.swift */; }; @@ -53,6 +55,12 @@ 6B7EED9E224CA5DD00060872 /* UIScrollViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EED9D224CA5DD00060872 /* UIScrollViewExtensions.swift */; }; 6B7EEDA0224CE4E100060872 /* UIScrollViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EED9F224CE4E000060872 /* UIScrollViewExtensionsTests.swift */; }; 6BAEA43D228D4E920026F81E /* RuntimeAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAEA43C228D4E920026F81E /* RuntimeAssociation.swift */; }; + 6BC83E4B231ED14700350855 /* CellsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC83E4A231ED14600350855 /* CellsBuilder.swift */; }; + 6BC83E4D231ED17300350855 /* CellsBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC83E4C231ED17300350855 /* CellsBuildable.swift */; }; + 6BC83E4F231ED1BB00350855 /* CellGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC83E4E231ED1BB00350855 /* CellGroup.swift */; }; + 6BC83E51231ED21700350855 /* SectionsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC83E50231ED21700350855 /* SectionsBuilder.swift */; }; + 6BC83E53231ED42C00350855 /* SectionsBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC83E52231ED42C00350855 /* SectionsBuildable.swift */; }; + 6BC83E55231ED44300350855 /* SectionGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC83E54231ED44300350855 /* SectionGroup.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -67,6 +75,8 @@ /* Begin PBXFileReference section */ 6B1BE6B622E5CA0D0054DB46 /* ComponentRenderable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComponentRenderable.swift; sourceTree = ""; }; + 6B37B2F32320339300A80D62 /* IdentifiedComponentWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiedComponentWrapper.swift; sourceTree = ""; }; + 6B37B2F42320339300A80D62 /* ComponentWrapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComponentWrapping.swift; sourceTree = ""; }; 6B5304522201EDB200A3E21E /* DataChangeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataChangeset.swift; sourceTree = ""; }; 6B65947A21E252E300291AAF /* Carbon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Carbon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6B65948621E2532100291AAF /* IdentifiableComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiableComponent.swift; sourceTree = ""; }; @@ -116,6 +126,12 @@ 6B7EED9D224CA5DD00060872 /* UIScrollViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollViewExtensions.swift; sourceTree = ""; }; 6B7EED9F224CE4E000060872 /* UIScrollViewExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollViewExtensionsTests.swift; sourceTree = ""; }; 6BAEA43C228D4E920026F81E /* RuntimeAssociation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeAssociation.swift; sourceTree = ""; }; + 6BC83E4A231ED14600350855 /* CellsBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellsBuilder.swift; sourceTree = ""; }; + 6BC83E4C231ED17300350855 /* CellsBuildable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellsBuildable.swift; sourceTree = ""; }; + 6BC83E4E231ED1BB00350855 /* CellGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellGroup.swift; sourceTree = ""; }; + 6BC83E50231ED21700350855 /* SectionsBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionsBuilder.swift; sourceTree = ""; }; + 6BC83E52231ED42C00350855 /* SectionsBuildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionsBuildable.swift; sourceTree = ""; }; + 6BC83E54231ED44300350855 /* SectionGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionGroup.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -169,6 +185,7 @@ 6B65949021E2532100291AAF /* Section.swift */, 6B6594A321E2532100291AAF /* Renderer.swift */, 6B5304522201EDB200A3E21E /* DataChangeset.swift */, + 6BC83E47231ECE5000350855 /* FunctionBuilder */, 6B65948921E2532100291AAF /* Adapters */, 6B65949221E2532100291AAF /* Updaters */, 6B65949D21E2532100291AAF /* Interfaces */, @@ -298,6 +315,21 @@ path = Interfaces; sourceTree = ""; }; + 6BC83E47231ECE5000350855 /* FunctionBuilder */ = { + isa = PBXGroup; + children = ( + 6BC83E4A231ED14600350855 /* CellsBuilder.swift */, + 6BC83E4C231ED17300350855 /* CellsBuildable.swift */, + 6BC83E4E231ED1BB00350855 /* CellGroup.swift */, + 6BC83E50231ED21700350855 /* SectionsBuilder.swift */, + 6BC83E52231ED42C00350855 /* SectionsBuildable.swift */, + 6BC83E54231ED44300350855 /* SectionGroup.swift */, + 6B37B2F42320339300A80D62 /* ComponentWrapping.swift */, + 6B37B2F32320339300A80D62 /* IdentifiedComponentWrapper.swift */, + ); + path = FunctionBuilder; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -434,15 +466,23 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6B37B2F52320339300A80D62 /* IdentifiedComponentWrapper.swift in Sources */, 6B6594BB21E2532100291AAF /* UITableViewComponentHeaderFooterView.swift in Sources */, 6B7EED9E224CA5DD00060872 /* UIScrollViewExtensions.swift in Sources */, + 6BC83E53231ED42C00350855 /* SectionsBuildable.swift in Sources */, 6B6594AC21E2532100291AAF /* Section.swift in Sources */, + 6BC83E4B231ED14700350855 /* CellsBuilder.swift in Sources */, 6B6594B121E2532100291AAF /* UICollectionViewReloadDataUpdater.swift in Sources */, + 6BC83E4D231ED17300350855 /* CellsBuildable.swift in Sources */, 6B6594B421E2532100291AAF /* Component.swift in Sources */, 6B6594B821E2532100291AAF /* UICollectionComponentReusableView.swift in Sources */, 6BAEA43D228D4E920026F81E /* RuntimeAssociation.swift in Sources */, + 6BC83E51231ED21700350855 /* SectionsBuilder.swift in Sources */, 6B6594A621E2532100291AAF /* UICollectionViewAdapter.swift in Sources */, + 6B37B2F62320339300A80D62 /* ComponentWrapping.swift in Sources */, + 6BC83E4F231ED1BB00350855 /* CellGroup.swift in Sources */, 6B6594A921E2532100291AAF /* Adapter.swift in Sources */, + 6BC83E55231ED44300350855 /* SectionGroup.swift in Sources */, 6B6594A721E2532100291AAF /* UITableViewAdapter.swift in Sources */, 6B6594AD21E2532100291AAF /* ViewNode.swift in Sources */, 6B6594A421E2532100291AAF /* IdentifiableComponent.swift in Sources */, diff --git a/Examples/Example-iOS/Sources/Common/FooterContent.xib b/Examples/Example-iOS/Sources/Common/FooterContent.xib index c91e35d..c206102 100644 --- a/Examples/Example-iOS/Sources/Common/FooterContent.xib +++ b/Examples/Example-iOS/Sources/Common/FooterContent.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -17,8 +16,8 @@ diff --git a/Examples/Example-iOS/Sources/Common/HeaderContent.xib b/Examples/Example-iOS/Sources/Common/HeaderContent.xib index 91ef7f0..a27c7e7 100644 --- a/Examples/Example-iOS/Sources/Common/HeaderContent.xib +++ b/Examples/Example-iOS/Sources/Common/HeaderContent.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -17,8 +16,8 @@ @@ -42,7 +41,7 @@ - + diff --git a/Examples/Example-iOS/Sources/Common/Spacing.swift b/Examples/Example-iOS/Sources/Common/Spacing.swift index b62f6e9..ebfb1ad 100644 --- a/Examples/Example-iOS/Sources/Common/Spacing.swift +++ b/Examples/Example-iOS/Sources/Common/Spacing.swift @@ -1,7 +1,7 @@ import UIKit import Carbon -struct Spacing: IdentifiableComponent, Hashable { +struct Spacing: Component { var height: CGFloat func renderContent() -> UIView { diff --git a/Examples/Example-iOS/Sources/Emoji/EmojiViewController.swift b/Examples/Example-iOS/Sources/Emoji/EmojiViewController.swift index fcad095..c29482e 100644 --- a/Examples/Example-iOS/Sources/Emoji/EmojiViewController.swift +++ b/Examples/Example-iOS/Sources/Emoji/EmojiViewController.swift @@ -2,10 +2,6 @@ import UIKit import Carbon final class EmojiViewController: UIViewController { - enum ID { - case emoji - } - @IBOutlet var collectionView: UICollectionView! @IBOutlet var toolBar: UIToolbar! @@ -22,8 +18,7 @@ final class EmojiViewController: UIViewController { super.viewDidLoad() title = "Shuffle Emoji" - toolBar.isTranslucent = false - collectionView.contentInset.bottom = 44 + collectionView.contentInset.top = 44 renderer.target = collectionView @@ -31,19 +26,13 @@ final class EmojiViewController: UIViewController { } func render() { - renderer.render( - Section( - id: ID.emoji, - header: ViewNode(Header(title: "EMOJIS")), - cells: emojiCodes - .enumerated() - .map { offset, code in - CellNode(EmojiLabel(code: code) { [weak self] in - self?.emojiCodes.remove(at: offset) - }) + renderer.render { + CellGroup(of: emojiCodes.enumerated()) { offset, code in + EmojiLabel(code: code) { [weak self] in + self?.emojiCodes.remove(at: offset) } - ) - ) + } + } } @IBAction func refresh() { diff --git a/Examples/Example-iOS/Sources/Emoji/EmojiViewController.xib b/Examples/Example-iOS/Sources/Emoji/EmojiViewController.xib index b8c374e..3de5dfb 100644 --- a/Examples/Example-iOS/Sources/Emoji/EmojiViewController.xib +++ b/Examples/Example-iOS/Sources/Emoji/EmojiViewController.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -22,7 +21,7 @@ - + @@ -32,7 +31,7 @@ - + @@ -72,7 +71,7 @@ - + diff --git a/Examples/Example-iOS/Sources/Form/FormViewController.swift b/Examples/Example-iOS/Sources/Form/FormViewController.swift index feead34..df28181 100644 --- a/Examples/Example-iOS/Sources/Form/FormViewController.swift +++ b/Examples/Example-iOS/Sources/Form/FormViewController.swift @@ -6,7 +6,6 @@ final class FormViewController: UIViewController { case about case note case detail - case detailsInput case genderPicker case birthdayPicker } @@ -47,7 +46,7 @@ final class FormViewController: UIViewController { title = "Profile Form" NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) - tableView.estimatedRowHeight = 100 + tableView.contentInset.bottom = 150 renderer.target = tableView renderer.updater.deleteRowsAnimation = .middle renderer.updater.insertRowsAnimation = .middle @@ -58,80 +57,72 @@ final class FormViewController: UIViewController { } func render() { - renderer.render { sections in - sections += [ - Section(id: ID.about) { section in - section.header = ViewNode(Header(title: "ABOUT")) - - section.cells += [ - CellNode(FormTextField(title: "Name", text: state.name) { [weak self] text in - self?.state.name = text - }), - CellNode(FormLabel(title: "Gender", text: state.gender?.rawValue) { [weak self] in - self?.state.isOpenGenderPicker.toggle() - }) - ] - - if state.isOpenGenderPicker { - section.cells += [ - CellNode(id: ID.genderPicker, FormTextPicker(texts: Gender.allTexts) { [weak self] text in - self?.state.gender = Gender(rawValue: text) - }) - ] + renderer.render { + Section(id: ID.about) { + Header(title: "ABOUT") + .identified(by: \.title) + + FormTextField(title: "Name", text: state.name) { [weak self] text in + self?.state.name = text + } + + FormLabel(title: "Gender", text: state.gender?.rawValue) { [weak self] in + self?.state.isOpenGenderPicker.toggle() + } + + if state.isOpenGenderPicker { + FormTextPicker(texts: Gender.allTexts) { [weak self] text in + self?.state.gender = Gender(rawValue: text) } + .identified(by: ID.genderPicker) + } - section.cells += [ - CellNode(FormLabel(title: "Birthday", text: state.birthday?.longText) { [weak self] in - self?.state.isOpenBirthdayPicker.toggle() - }) - ] - - if state.isOpenBirthdayPicker { - section.cells += [ - CellNode(id: ID.birthdayPicker, FormDatePicker(date: state.birthday ?? Date()) { [weak self] date in - self?.state.birthday = date - }) - ] + FormLabel(title: "Birthday", text: state.birthday?.longText) { [weak self] in + self?.state.isOpenBirthdayPicker.toggle() + } + + if state.isOpenBirthdayPicker { + FormDatePicker(date: state.birthday ?? Date()) { [weak self] date in + self?.state.birthday = date + } + .identified(by: ID.birthdayPicker) + } + } + + Section(id: ID.note) { + Header(title: "NOTE") + .identified(by: \.title) + + FormTextView(text: state.note) { [weak self] text in + self?.state.note = text + } + .identified(by: ID.note) + } + + Section(id: ID.detail) { + Header(title: "DETAILS") + .identified(by: \.title) + + FormSwitch(title: "Show Details", isOn: state.isOpenDetails) { [weak self] isOn in + self?.state.isOpenDetails = isOn + } + + if state.isOpenDetails { + Spacing(height: 12) + .identified(by: ID.detail) + + FormTextField(title: "Location", text: state.location) { [weak self] text in + self?.state.location = text + } + + FormTextField(title: "Email", text: state.email, keyboardType: .emailAddress) { [weak self] text in + self?.state.email = text + } + + FormTextField(title: "Job", text: state.job) { [weak self] text in + self?.state.job = text } - }, - Section( - id: ID.note, - header: ViewNode(Header(title: "NOTE")), - cells: [ - CellNode(id: ID.note, FormTextView(text: state.note) { [weak self] text in - self?.state.note = text - }) - ] - ), - Section( - id: ID.detail, - header: ViewNode(Header(title: "DETAILS")), - cells: [ - CellNode(FormSwitch(title: "Show Details", isOn: state.isOpenDetails) { [weak self] isOn in - self?.state.isOpenDetails = isOn - }) - ] - ) - ] - - if state.isOpenDetails { - sections += [ - Section( - id: ID.detailsInput, - header: ViewNode(Spacing(height: 12)), - cells: [ - CellNode(FormTextField(title: "Location", text: state.location) { [weak self] text in - self?.state.location = text - }), - CellNode(FormTextField(title: "Email", text: state.email, keyboardType: .emailAddress) { [weak self] text in - self?.state.email = text - }), - CellNode(FormTextField(title: "Job", text: state.job) { [weak self] text in - self?.state.job = text - }) - ] - ) - ] + } } } } diff --git a/Examples/Example-iOS/Sources/Form/FormViewController.xib b/Examples/Example-iOS/Sources/Form/FormViewController.xib index 5b11968..7a835c8 100644 --- a/Examples/Example-iOS/Sources/Form/FormViewController.xib +++ b/Examples/Example-iOS/Sources/Form/FormViewController.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -21,7 +20,7 @@ - + diff --git a/Examples/Example-iOS/Sources/Hello/HelloMessageContent.xib b/Examples/Example-iOS/Sources/Hello/HelloMessageContent.xib index feb0fc1..b70b1a3 100644 --- a/Examples/Example-iOS/Sources/Hello/HelloMessageContent.xib +++ b/Examples/Example-iOS/Sources/Hello/HelloMessageContent.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -13,15 +12,15 @@ - + diff --git a/Examples/Example-iOS/Sources/Hello/HelloViewController.swift b/Examples/Example-iOS/Sources/Hello/HelloViewController.swift index dfd9ff8..d2868b1 100644 --- a/Examples/Example-iOS/Sources/Hello/HelloViewController.swift +++ b/Examples/Example-iOS/Sources/Hello/HelloViewController.swift @@ -2,10 +2,6 @@ import UIKit import Carbon final class HelloViewController: UIViewController { - enum ID { - case greet - } - @IBOutlet var tableView: UITableView! var isToggled = false { @@ -28,27 +24,23 @@ final class HelloViewController: UIViewController { } func render() { - renderer.render( - Section(id: ID.greet) { section in - section.header = ViewNode(Header(title: "GREET")) - - if isToggled { - section.cells = [ - CellNode(HelloMessage(name: "Jules")), - CellNode(HelloMessage(name: "Vincent")) - ] - } - else { - section.cells = [ - CellNode(HelloMessage(name: "Vincent")), - CellNode(HelloMessage(name: "Jules")), - CellNode(HelloMessage(name: "Butch")) - ] - } - - section.footer = ViewNode(Footer(text: "💡 Tap anywhere")) + renderer.render { + Header(title: "GREET") + .identified(by: \.title) + + if isToggled { + HelloMessage(name: "Jules") + HelloMessage(name: "Vincent") } - ) + else { + HelloMessage(name: "Vincent") + HelloMessage(name: "Jules") + HelloMessage(name: "Butch") + } + + Footer(text: "Tap anywhere 💡") + .identified(by: \.text) + } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { diff --git a/Examples/Example-iOS/Sources/Hello/HelloViewController.xib b/Examples/Example-iOS/Sources/Hello/HelloViewController.xib index 8da99bb..6528183 100644 --- a/Examples/Example-iOS/Sources/Hello/HelloViewController.xib +++ b/Examples/Example-iOS/Sources/Hello/HelloViewController.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -21,7 +20,7 @@ - + @@ -34,6 +33,7 @@ + diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoLicenseContent.xib b/Examples/Example-iOS/Sources/Kyoto/KyotoLicenseContent.xib index a82943c..669bebb 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoLicenseContent.xib +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoLicenseContent.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -17,10 +16,10 @@ @@ -29,7 +28,7 @@ by Unsplash - + diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoTop.swift b/Examples/Example-iOS/Sources/Kyoto/KyotoTop.swift index 262ad4e..fa9f456 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoTop.swift +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoTop.swift @@ -1,7 +1,7 @@ import UIKit import Carbon -struct KyotoTop: Component { +struct KyotoTop: Component, Hashable { func renderContent() -> KyotoTopContent { return .loadFromNib() } diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.swift b/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.swift index 12007fc..e57aaef 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.swift +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.swift @@ -19,11 +19,6 @@ final class KyotoViewController: UIViewController { return .portrait } - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - collectionView.performBatchUpdates(nil) - } - override func viewDidLoad() { super.viewDidLoad() @@ -31,29 +26,34 @@ final class KyotoViewController: UIViewController { let layout = MagazineLayout() collectionView.collectionViewLayout = layout - renderer.target = collectionView - renderer.render( + + renderer.render { Section( id: ID.top, - header: ViewNode(KyotoTop()) - ), + header: KyotoTop() + ) + Section( id: ID.photo, - header: ViewNode(Header(title: "PHOTOS")), - cells: [ - CellNode(KyotoImage(title: "Fushimi Inari-taisha", image: #imageLiteral(resourceName: "KyotoFushimiInari"))), - CellNode(KyotoImage(title: "Arashiyama", image: #imageLiteral(resourceName: "KyotoArashiyama"))), - CellNode(KyotoImage(title: "Byōdō-in", image: #imageLiteral(resourceName: "KyotoByōdōIn"))), - CellNode(KyotoImage(title: "Gion", image: #imageLiteral(resourceName: "KyotoGion"))), - CellNode(KyotoImage(title: "Kiyomizu-dera", image: #imageLiteral(resourceName: "KyotoKiyomizuDera"))) - ], - footer: ViewNode(KyotoLicense { + header: Header(title: "PHOTOS"), + footer: KyotoLicense { let url = URL(string: "https://unsplash.com/")! UIApplication.shared.open(url) - }) - ) - ) + }, + cells: { + KyotoImage(title: "Fushimi Inari-taisha", image: #imageLiteral(resourceName: "KyotoFushimiInari")) + KyotoImage(title: "Arashiyama", image: #imageLiteral(resourceName: "KyotoArashiyama")) + KyotoImage(title: "Byōdō-in", image: #imageLiteral(resourceName: "KyotoByōdōIn")) + KyotoImage(title: "Gion", image: #imageLiteral(resourceName: "KyotoGion")) + KyotoImage(title: "Kiyomizu-dera", image: #imageLiteral(resourceName: "KyotoKiyomizuDera")) + }) + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.performBatchUpdates(nil) } } diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.xib b/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.xib index 96b4911..a51badf 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.xib +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -21,7 +20,7 @@ - + diff --git a/Examples/Example-iOS/Sources/Pangram/PangramLabel.swift b/Examples/Example-iOS/Sources/Pangram/PangramLabel.swift index 523039b..c367008 100644 --- a/Examples/Example-iOS/Sources/Pangram/PangramLabel.swift +++ b/Examples/Example-iOS/Sources/Pangram/PangramLabel.swift @@ -8,7 +8,7 @@ struct PangramLabel: IdentifiableComponent, Hashable { let label = UILabel() label.textColor = .primaryGreen label.textAlignment = .center - label.font = .boldSystemFont(ofSize: 18) + label.font = .boldSystemFont(ofSize: 20) label.backgroundColor = UIColor.primaryWhite.withAlphaComponent(0.1) return label } diff --git a/Examples/Example-iOS/Sources/Pangram/PangramViewController.swift b/Examples/Example-iOS/Sources/Pangram/PangramViewController.swift index f058b60..ff0743a 100644 --- a/Examples/Example-iOS/Sources/Pangram/PangramViewController.swift +++ b/Examples/Example-iOS/Sources/Pangram/PangramViewController.swift @@ -18,7 +18,6 @@ final class PangramViewController: UIViewController { super.viewDidLoad() title = "Pangram" - toolBar.isTranslucent = false collectionView.contentInset.top = 44 renderer.target = collectionView @@ -26,21 +25,17 @@ final class PangramViewController: UIViewController { } func render() { - renderer.render { sections in - let pangram = isSorted - ? "ABC DEF GHI JKL MNO PQR STU VWY XZ" - : "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG" - - sections = pangram - .split(separator: " ") - .enumerated() - .map { offset, word in - Section( - id: offset, - cells: word.map { text in - CellNode(PangramLabel(text: String(text))) - } - ) + let pangram = isSorted + ? ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VWY", "XZ"] + : ["THE", "QUICK", "BROWN", "FOX", "JUMPS", "OVER", "THE", "LAZY", "DOG"] + + renderer.render { + SectionGroup(of: pangram.enumerated()) { offset, word in + Section(id: offset) { + CellGroup(of: word) { text in + PangramLabel(text: String(text)) + } + } } } } diff --git a/Examples/Example-iOS/Sources/Pangram/PangramViewController.xib b/Examples/Example-iOS/Sources/Pangram/PangramViewController.xib index 9d34aeb..b80ba38 100644 --- a/Examples/Example-iOS/Sources/Pangram/PangramViewController.xib +++ b/Examples/Example-iOS/Sources/Pangram/PangramViewController.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -22,7 +21,7 @@ - + @@ -32,7 +31,7 @@ - + diff --git a/Examples/Example-iOS/Sources/Todo/TodoEmpty.swift b/Examples/Example-iOS/Sources/Todo/TodoEmpty.swift index 1660871..3dd33fa 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoEmpty.swift +++ b/Examples/Example-iOS/Sources/Todo/TodoEmpty.swift @@ -1,7 +1,7 @@ import UIKit import Carbon -struct TodoEmpty: Component { +struct TodoEmpty: IdentifiableComponent, Hashable { func renderContent() -> TodoEmptyContent { return .loadFromNib() } diff --git a/Examples/Example-iOS/Sources/Todo/TodoEmptyContent.xib b/Examples/Example-iOS/Sources/Todo/TodoEmptyContent.xib index bcbeb88..e18f6c0 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoEmptyContent.xib +++ b/Examples/Example-iOS/Sources/Todo/TodoEmptyContent.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -17,12 +16,12 @@ diff --git a/Examples/Example-iOS/Sources/Todo/TodoTextContent.xib b/Examples/Example-iOS/Sources/Todo/TodoTextContent.xib index 1746352..f544479 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoTextContent.xib +++ b/Examples/Example-iOS/Sources/Todo/TodoTextContent.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -65,7 +64,7 @@ - + diff --git a/Examples/Example-iOS/Sources/Todo/TodoViewController.swift b/Examples/Example-iOS/Sources/Todo/TodoViewController.swift index 33280a1..b18a574 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoViewController.swift +++ b/Examples/Example-iOS/Sources/Todo/TodoViewController.swift @@ -40,7 +40,6 @@ final class TodoViewController: UIViewController, UITextViewDelegate { NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) - tableView.estimatedRowHeight = 70 tableView.contentInset.bottom = view.bounds.height - addButton.frame.minY renderer.target = tableView @@ -48,44 +47,50 @@ final class TodoViewController: UIViewController, UITextViewDelegate { } func render() { - renderer.render( - Section(id: ID.task) { section in - section.header = state.todos.isEmpty - ? ViewNode(TodoEmpty()) - : ViewNode(Header(title: "TASKS (\(state.todos.count))")) - - section.cells = state.todos.enumerated().map { offset, todo in - CellNode(TodoText(todo: todo, isCompleted: false) { [weak self] event in - switch event { - case .toggleCompleted: - self?.state.todos.remove(at: offset) - self?.state.completed.insert(todo, at: 0) - - case .delete: - self?.state.todos.remove(at: offset) + renderer.render { + if state.todos.isEmpty { + Section(id: ID.task, header: TodoEmpty()) + } + else { + Section( + id: ID.task, + header: Header(title: "TASKS (\(state.todos.count))"), + cells: { + CellGroup(of: state.todos.enumerated()) { offset, todo in + TodoText(todo: todo, isCompleted: false) { [weak self] event in + switch event { + case .toggleCompleted: + self?.state.todos.remove(at: offset) + self?.state.completed.append(todo) + + case .delete: + self?.state.todos.remove(at: offset) + } + } } - }) - } - }, - Section(id: ID.completed) { section in - if !state.completed.isEmpty { - section.header = ViewNode(Header(title: "COMPLETED (\(state.completed.count))")) - } - - section.cells = state.completed.enumerated().map { offset, todo in - CellNode(TodoText(todo: todo, isCompleted: true) { [weak self] event in - switch event { - case .toggleCompleted: - self?.state.completed.remove(at: offset) - self?.state.todos.insert(todo, at: 0) - - case .delete: - self?.state.completed.remove(at: offset) + }) + } + + if !state.completed.isEmpty { + Section( + id: ID.completed, + header: Header(title: "COMPLETED (\(state.completed.count))"), + cells: { + CellGroup(of: state.completed.enumerated()) { offset, todo in + TodoText(todo: todo, isCompleted: true) { [weak self] event in + switch event { + case .toggleCompleted: + self?.state.completed.remove(at: offset) + self?.state.todos.append(todo) + + case .delete: + self?.state.completed.remove(at: offset) + } + } } - }) - } + }) } - ) + } } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { diff --git a/Examples/Example-iOS/Sources/Todo/TodoViewController.xib b/Examples/Example-iOS/Sources/Todo/TodoViewController.xib index 2e38546..422a465 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoViewController.xib +++ b/Examples/Example-iOS/Sources/Todo/TodoViewController.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -26,7 +25,7 @@ - + @@ -38,7 +37,7 @@ - + @@ -108,7 +107,7 @@ - + diff --git a/Examples/Example-iOS/Sources/Top/HomeItemContent.xib b/Examples/Example-iOS/Sources/Top/HomeItemContent.xib index 3fe1caf..8ead29a 100644 --- a/Examples/Example-iOS/Sources/Top/HomeItemContent.xib +++ b/Examples/Example-iOS/Sources/Top/HomeItemContent.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -17,11 +16,11 @@ diff --git a/Examples/Example-iOS/Sources/Top/HomeViewController.swift b/Examples/Example-iOS/Sources/Top/HomeViewController.swift index c0b44ba..01a2c6d 100644 --- a/Examples/Example-iOS/Sources/Top/HomeViewController.swift +++ b/Examples/Example-iOS/Sources/Top/HomeViewController.swift @@ -2,10 +2,6 @@ import UIKit import Carbon final class HomeViewController: UIViewController { - enum ID { - case examples - } - enum Destination { case hello case pangram @@ -26,37 +22,39 @@ final class HomeViewController: UIViewController { super.viewDidLoad() title = "Home" - renderer.target = tableView - renderer.render( - Section( - id: ID.examples, - header: ViewNode(Header(title: "EXAMPLES")), - cells: [ - CellNode(HomeItem(title: "👋 Hello") { [weak self] in - self?.push(to: .hello) - }), - CellNode(HomeItem(title: "🔠 Pangram") { [weak self] in - self?.push(to: .pangram) - }), - CellNode(HomeItem(title: "⛩ Kyoto") { [weak self] in - self?.push(to: .kyoto) - }), - CellNode(HomeItem(title: "😀 Shuffle Emoji") { [weak self] in - self?.push(to: .emoji) - }), - CellNode(HomeItem(title: "📋 Todo App") { [weak self] in - self?.push(to: .todo) - }), - CellNode(HomeItem(title: "👤 Profile Form") { [weak self] in - self?.push(to: .form) - }) - ] - ) - ) + + renderer.render { + Header(title: "EXAMPLES") + .identified(by: \.title) + + HomeItem(title: "👋 Hello") { [weak self] in + self?.push(.hello) + } + + HomeItem(title: "🔠 Pangram") { [weak self] in + self?.push(.pangram) + } + + HomeItem(title: "⛩ Kyoto") { [weak self] in + self?.push(.kyoto) + } + + HomeItem(title: "😀 Shuffle Emoji") { [weak self] in + self?.push(.emoji) + } + + HomeItem(title: "📋 Todo App") { [weak self] in + self?.push(.todo) + } + + HomeItem(title: "👤 Profile Form") { [weak self] in + self?.push(.form) + } + } } - func push(to destination: Destination) { + func push(_ destination: Destination) { let controller: UIViewController switch destination { diff --git a/Examples/Example-iOS/Sources/Top/HomeViewController.xib b/Examples/Example-iOS/Sources/Top/HomeViewController.xib index b21d4e6..637ab33 100644 --- a/Examples/Example-iOS/Sources/Top/HomeViewController.xib +++ b/Examples/Example-iOS/Sources/Top/HomeViewController.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -21,7 +20,7 @@ - + @@ -34,6 +33,7 @@ + diff --git a/Sources/CellNode.swift b/Sources/CellNode.swift index 8e9bba5..2dfd212 100644 --- a/Sources/CellNode.swift +++ b/Sources/CellNode.swift @@ -48,6 +48,12 @@ public struct CellNode { } } +extension CellNode: CellsBuildable { + public func buildCells() -> [CellNode] { + [self] + } +} + extension CellNode: Differentiable { /// An identifier value for difference calculation. @inlinable diff --git a/Sources/Component.swift b/Sources/Component.swift index f5a2e49..3666264 100644 --- a/Sources/Component.swift +++ b/Sources/Component.swift @@ -168,6 +168,16 @@ public extension Component { func contentDidEndDisplay(_ content: Content) {} } +public extension Component { + func identified(by keyPath: KeyPath) -> IdentifiedComponentWrapper { + IdentifiedComponentWrapper(id: self[keyPath: keyPath], wrapped: self) + } + + func identified(by id: ID) -> IdentifiedComponentWrapper { + IdentifiedComponentWrapper(id: id, wrapped: self) + } +} + public extension Component where Content: UIView { /// Layout the content on top of element of the list UI. /// diff --git a/Sources/FunctionBuilder/CellGroup.swift b/Sources/FunctionBuilder/CellGroup.swift new file mode 100644 index 0000000..8ac7b8d --- /dev/null +++ b/Sources/FunctionBuilder/CellGroup.swift @@ -0,0 +1,19 @@ +public struct CellGroup: CellsBuildable { + private var _buildCells: () -> [CellNode] + + public init(@CellsBuilder _ cells: @escaping () -> C) { + _buildCells = cells().buildCells + } + + public init(of sequence: Seq, cell: @escaping (Seq.Element) -> C) { + _buildCells = { + sequence.flatMap { element in + cell(element).buildCells() + } + } + } + + public func buildCells() -> [CellNode] { + _buildCells() + } +} diff --git a/Sources/FunctionBuilder/CellsBuildable.swift b/Sources/FunctionBuilder/CellsBuildable.swift new file mode 100644 index 0000000..442c0c1 --- /dev/null +++ b/Sources/FunctionBuilder/CellsBuildable.swift @@ -0,0 +1,9 @@ +public protocol CellsBuildable { + func buildCells() -> [CellNode] +} + +extension Optional: CellsBuildable where Wrapped: CellsBuildable { + public func buildCells() -> [CellNode] { + self?.buildCells() ?? [] + } +} diff --git a/Sources/FunctionBuilder/CellsBuilder.swift b/Sources/FunctionBuilder/CellsBuilder.swift new file mode 100644 index 0000000..bc1ea8b --- /dev/null +++ b/Sources/FunctionBuilder/CellsBuilder.swift @@ -0,0 +1,131 @@ +// swiftlint:disable line_length +// swiftlint:disable function_parameter_count + +@_functionBuilder +public struct CellsBuilder: CellsBuildable { + private var cellNodes: [CellNode] + + public func buildCells() -> [CellNode] { + cellNodes + } + + public static func buildBlock() -> CellsBuilder { + CellsBuilder() + } + + public static func buildBlock(_ c: C) -> CellsBuilder { + CellsBuilder(c) + } + + public static func buildBlock(_ c0: C0, _ c1: C1) -> CellsBuilder { + CellsBuilder(c0, c1) + } + + public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2) -> CellsBuilder { + CellsBuilder(c0, c1, c2) + } + + public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3) -> CellsBuilder { + CellsBuilder(c0, c1, c2, c3) + } + + public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4) -> CellsBuilder { + CellsBuilder(c0, c1, c2, c3, c4) + } + + public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5) -> CellsBuilder { + CellsBuilder(c0, c1, c2, c3, c4, c5) + } + + public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6) -> CellsBuilder { + CellsBuilder(c0, c1, c2, c3, c4, c5, c6) + } + + public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7) -> CellsBuilder { + CellsBuilder(c0, c1, c2, c3, c4, c5, c6, c7) + } + + public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8) -> CellsBuilder { + CellsBuilder(c0, c1, c2, c3, c4, c5, c6, c7, c8) + } + + public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> CellsBuilder { + CellsBuilder(c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) + } + + public static func buildIf(_ c: C?) -> C? { + c + } + + public static func buildEither(first: C) -> C { + first + } + + public static func buildEither(second: C) -> C { + second + } +} + +private extension CellsBuilder { + init() { + cellNodes = [] + } + + init(_ c: C) { + cellNodes = c.buildCells() + } + + init(_ c0: C0, _ c1: C1) { + cellNodes = c0.buildCells() + c1.buildCells() + } + + init(_ c0: C0, _ c1: C1, _ c2: C2) { + cellNodes = c0.buildCells() + c1.buildCells() + c2.buildCells() + } + + init(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3) { + let cellNodes0 = c0.buildCells() + c1.buildCells() + c2.buildCells() + let cellNodes1 = c3.buildCells() + cellNodes = cellNodes0 + cellNodes1 + } + + init(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4) { + let cellNodes0 = c0.buildCells() + c1.buildCells() + c2.buildCells() + let cellNodes1 = c3.buildCells() + c4.buildCells() + cellNodes = cellNodes0 + cellNodes1 + } + + init(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5) { + let cellNodes0 = c0.buildCells() + c1.buildCells() + c2.buildCells() + let cellNodes1 = c3.buildCells() + c4.buildCells() + c5.buildCells() + cellNodes = cellNodes0 + cellNodes1 + } + + init(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6) { + let cellNodes0 = c0.buildCells() + c1.buildCells() + c2.buildCells() + let cellNodes1 = c3.buildCells() + c4.buildCells() + c5.buildCells() + let cellNodes2 = c6.buildCells() + cellNodes = cellNodes0 + cellNodes1 + cellNodes2 + } + + init(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7) { + let cellNodes0 = c0.buildCells() + c1.buildCells() + c2.buildCells() + let cellNodes1 = c3.buildCells() + c4.buildCells() + c5.buildCells() + let cellNodes2 = c6.buildCells() + c7.buildCells() + cellNodes = cellNodes0 + cellNodes1 + cellNodes2 + } + + init(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8) { + let cellNodes0 = c0.buildCells() + c1.buildCells() + c2.buildCells() + let cellNodes1 = c3.buildCells() + c4.buildCells() + c5.buildCells() + let cellNodes2 = c6.buildCells() + c7.buildCells() + c8.buildCells() + cellNodes = cellNodes0 + cellNodes1 + cellNodes2 + } + + init(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) { + let cellNodes0 = c0.buildCells() + c1.buildCells() + c2.buildCells() + let cellNodes1 = c3.buildCells() + c4.buildCells() + c5.buildCells() + let cellNodes2 = c6.buildCells() + c7.buildCells() + c8.buildCells() + cellNodes = cellNodes0 + cellNodes1 + cellNodes2 + c9.buildCells() + } +} diff --git a/Sources/FunctionBuilder/ComponentWrapping.swift b/Sources/FunctionBuilder/ComponentWrapping.swift new file mode 100644 index 0000000..470fed1 --- /dev/null +++ b/Sources/FunctionBuilder/ComponentWrapping.swift @@ -0,0 +1,45 @@ +import UIKit + +public protocol ComponentWrapping: Component { + associatedtype Wrapped: Component + + var wrapped: Wrapped { get } +} + +public extension ComponentWrapping { + var reuseIdentifier: String { + wrapped.reuseIdentifier + } + + func renderContent() -> Wrapped.Content { + wrapped.renderContent() + } + + func render(in content: Wrapped.Content) { + wrapped.render(in: content) + } + + func referenceSize(in bounds: CGRect) -> CGSize? { + wrapped.referenceSize(in: bounds) + } + + func shouldContentUpdate(with next: Wrapped) -> Bool { + wrapped.shouldContentUpdate(with: next) + } + + func shouldRender(next: Wrapped, in content: Wrapped.Content) -> Bool { + wrapped.shouldRender(next: next, in: content) + } + + func layout(content: Wrapped.Content, in container: UIView) { + wrapped.layout(content: content, in: container) + } + + func contentWillDisplay(_ content: Wrapped.Content) { + wrapped.contentWillDisplay(content) + } + + func contentDidEndDisplay(_ content: Wrapped.Content) { + wrapped.contentDidEndDisplay(content) + } +} diff --git a/Sources/FunctionBuilder/IdentifiedComponentWrapper.swift b/Sources/FunctionBuilder/IdentifiedComponentWrapper.swift new file mode 100644 index 0000000..7b31352 --- /dev/null +++ b/Sources/FunctionBuilder/IdentifiedComponentWrapper.swift @@ -0,0 +1,9 @@ +public struct IdentifiedComponentWrapper: IdentifiableComponent, ComponentWrapping { + public var id: ID + public var wrapped: Wrapped + + public init(id: ID, wrapped: Wrapped) { + self.id = id + self.wrapped = wrapped + } +} diff --git a/Sources/FunctionBuilder/SectionGroup.swift b/Sources/FunctionBuilder/SectionGroup.swift new file mode 100644 index 0000000..f5dcdf0 --- /dev/null +++ b/Sources/FunctionBuilder/SectionGroup.swift @@ -0,0 +1,19 @@ +public struct SectionGroup: SectionsBuildable { + private var _buildSections: () -> [Section] + + public init(@SectionsBuilder _ sections: @escaping () -> S) { + _buildSections = sections().buildSections + } + + public init(of sequence: Seq, section: @escaping (Seq.Element) -> S) { + _buildSections = { + sequence.flatMap { element in + section(element).buildSections() + } + } + } + + public func buildSections() -> [Section] { + _buildSections() + } +} diff --git a/Sources/FunctionBuilder/SectionsBuildable.swift b/Sources/FunctionBuilder/SectionsBuildable.swift new file mode 100644 index 0000000..694f20a --- /dev/null +++ b/Sources/FunctionBuilder/SectionsBuildable.swift @@ -0,0 +1,9 @@ +public protocol SectionsBuildable { + func buildSections() -> [Section] +} + +extension Optional: SectionsBuildable where Wrapped: SectionsBuildable { + public func buildSections() -> [Section] { + self?.buildSections() ?? [] + } +} diff --git a/Sources/FunctionBuilder/SectionsBuilder.swift b/Sources/FunctionBuilder/SectionsBuilder.swift new file mode 100644 index 0000000..9bd0040 --- /dev/null +++ b/Sources/FunctionBuilder/SectionsBuilder.swift @@ -0,0 +1,131 @@ +// swiftlint:disable line_length +// swiftlint:disable function_parameter_count + +@_functionBuilder +public struct SectionsBuilder: SectionsBuildable { + private var sections: [Section] + + public func buildSections() -> [Section] { + sections + } + + public static func buildBlock() -> SectionsBuilder { + SectionsBuilder() + } + + public static func buildBlock(_ c: S) -> SectionsBuilder { + SectionsBuilder(c) + } + + public static func buildBlock(_ s0: S0, _ s1: S1) -> SectionsBuilder { + SectionsBuilder(s0, s1) + } + + public static func buildBlock(_ s0: S0, _ s1: S1, _ s2: S2) -> SectionsBuilder { + SectionsBuilder(s0, s1, s2) + } + + public static func buildBlock(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3) -> SectionsBuilder { + SectionsBuilder(s0, s1, s2, s3) + } + + public static func buildBlock(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4) -> SectionsBuilder { + SectionsBuilder(s0, s1, s2, s3, s4) + } + + public static func buildBlock(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5) -> SectionsBuilder { + SectionsBuilder(s0, s1, s2, s3, s4, s5) + } + + public static func buildBlock(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5, _ s6: S6) -> SectionsBuilder { + SectionsBuilder(s0, s1, s2, s3, s4, s5, s6) + } + + public static func buildBlock(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5, _ s6: S6, _ s7: S7) -> SectionsBuilder { + SectionsBuilder(s0, s1, s2, s3, s4, s5, s6, s7) + } + + public static func buildBlock(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5, _ s6: S6, _ s7: S7, _ s8: S8) -> SectionsBuilder { + SectionsBuilder(s0, s1, s2, s3, s4, s5, s6, s7, s8) + } + + public static func buildBlock(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5, _ s6: S6, _ s7: S7, _ s8: S8, _ s9: S9) -> SectionsBuilder { + SectionsBuilder(s0, s1, s2, s3, s4, s5, s6, s7, s8, s9) + } + + public static func buildIf(_ s: S?) -> S? { + s + } + + public static func buildEither(first: S) -> S { + first + } + + public static func buildEither(second: S) -> S { + second + } +} + +private extension SectionsBuilder { + init() { + sections = [] + } + + init(_ s: S) { + sections = s.buildSections() + } + + init(_ s0: S0, _ s1: S1) { + sections = s0.buildSections() + s1.buildSections() + } + + init(_ s0: S0, _ s1: S1, _ s2: S2) { + sections = s0.buildSections() + s1.buildSections() + s2.buildSections() + } + + init(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3) { + let sections0 = s0.buildSections() + s1.buildSections() + s2.buildSections() + let sections1 = s3.buildSections() + sections = sections0 + sections1 + } + + init(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4) { + let sections0 = s0.buildSections() + s1.buildSections() + s2.buildSections() + let sections1 = s3.buildSections() + s4.buildSections() + sections = sections0 + sections1 + } + + init(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5) { + let sections0 = s0.buildSections() + s1.buildSections() + s2.buildSections() + let sections1 = s3.buildSections() + s4.buildSections() + s5.buildSections() + sections = sections0 + sections1 + } + + init(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5, _ s6: S6) { + let sections0 = s0.buildSections() + s1.buildSections() + s2.buildSections() + let sections1 = s3.buildSections() + s4.buildSections() + s5.buildSections() + let sections2 = s6.buildSections() + sections = sections0 + sections1 + sections2 + } + + init(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5, _ s6: S6, _ s7: S7) { + let sections0 = s0.buildSections() + s1.buildSections() + s2.buildSections() + let sections1 = s3.buildSections() + s4.buildSections() + s5.buildSections() + let sections2 = s6.buildSections() + s7.buildSections() + sections = sections0 + sections1 + sections2 + } + + init(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5, _ s6: S6, _ s7: S7, _ s8: S8) { + let sections0 = s0.buildSections() + s1.buildSections() + s2.buildSections() + let sections1 = s3.buildSections() + s4.buildSections() + s5.buildSections() + let sections2 = s6.buildSections() + s7.buildSections() + s8.buildSections() + sections = sections0 + sections1 + sections2 + } + + init(_ s0: S0, _ s1: S1, _ s2: S2, _ s3: S3, _ s4: S4, _ s5: S5, _ s6: S6, _ s7: S7, _ s8: S8, _ s9: S9) { + let sections0 = s0.buildSections() + s1.buildSections() + s2.buildSections() + let sections1 = s3.buildSections() + s4.buildSections() + s5.buildSections() + let sections2 = s6.buildSections() + s7.buildSections() + s8.buildSections() + sections = sections0 + sections1 + sections2 + s9.buildSections() + } +} diff --git a/Sources/IdentifiableComponent.swift b/Sources/IdentifiableComponent.swift index 1126d3d..c4a7700 100644 --- a/Sources/IdentifiableComponent.swift +++ b/Sources/IdentifiableComponent.swift @@ -17,7 +17,7 @@ /// /// let view = ViewNode(UserLabel(id: 0, name: "John")) /// let cell = CellNode(UserLabel(id: 0, name: "Jane")) -public protocol IdentifiableComponent: Component { +public protocol IdentifiableComponent: Component, CellsBuildable { /// A type that represents an id that used to uniquely identify the component. associatedtype ID: Hashable @@ -25,6 +25,12 @@ public protocol IdentifiableComponent: Component { var id: ID { get } } +public extension IdentifiableComponent { + func buildCells() -> [CellNode] { + return [CellNode(self)] + } +} + public extension IdentifiableComponent where Self: Hashable { /// An identifier that can be used to uniquely identify the component. /// Default is `self`. diff --git a/Sources/Renderer.swift b/Sources/Renderer.swift index 9778f73..e1aecc6 100644 --- a/Sources/Renderer.swift +++ b/Sources/Renderer.swift @@ -108,4 +108,19 @@ open class Renderer { buildData(&data) render(data) } + + open func render(@SectionsBuilder sections: () -> S) { + render(sections().buildSections()) + } + + open func render(@CellsBuilder cells: () -> C) { + render( + Section( + id: UniqueIdentifier(), + cells: cells().buildCells() + ) + ) + } } + +private struct UniqueIdentifier: Hashable {} diff --git a/Sources/Section.swift b/Sources/Section.swift index 914469c..61d3dc3 100644 --- a/Sources/Section.swift +++ b/Sources/Section.swift @@ -78,6 +78,66 @@ public struct Section { } } +public extension Section { + @inlinable + init(id: I, @CellsBuilder cells: () -> C) { + self.init(id: id, cells: cells().buildCells()) + } + + @inlinable + init(id: I, header: H?, footer: F?, @CellsBuilder cells: () -> C) { + self.init( + id: id, + header: header.map(ViewNode.init), + cells: cells().buildCells(), + footer: footer.map(ViewNode.init) + ) + } + + @inlinable + init(id: I, header: H?, footer: F?) { + self.init( + id: id, + header: header.map(ViewNode.init), + footer: footer.map(ViewNode.init) + ) + } + + @inlinable + init(id: I, header: H?, @CellsBuilder cells: () -> CellsBuildable) { + self.init( + id: id, + header: header.map(ViewNode.init), + cells: cells().buildCells() + ) + } + + @inlinable + init(id: I, header: H?) { + self.init(id: id, header: header.map(ViewNode.init)) + } + + @inlinable + init(id: I, footer: F?, @CellsBuilder cells: () -> C) { + self.init( + id: id, + cells: cells().buildCells(), + footer: footer.map(ViewNode.init) + ) + } + + @inlinable + init(id: I, footer: F?) { + self.init(id: id, footer: footer.map(ViewNode.init)) + } +} + +extension Section: SectionsBuildable { + public func buildSections() -> [Section] { + [self] + } +} + extension Section: DifferentiableSection { /// An identifier value for difference calculation. @inlinable From 15edf67a964fc65a170afca02d4bd206f111b79b Mon Sep 17 00:00:00 2001 From: Ryo Aoyama Date: Thu, 5 Sep 2019 04:18:19 +0900 Subject: [PATCH 02/21] Remove deprecated functions --- Sources/Renderer.swift | 11 ----------- Sources/Section.swift | 13 ------------- Tests/RendererTests.swift | 24 ------------------------ Tests/SectionTests.swift | 18 ------------------ 4 files changed, 66 deletions(-) diff --git a/Sources/Renderer.swift b/Sources/Renderer.swift index e1aecc6..bd17084 100644 --- a/Sources/Renderer.swift +++ b/Sources/Renderer.swift @@ -98,17 +98,6 @@ open class Renderer { render(data.compactMap { $0 }) } - /// Render sections built by given closure, immediately. - /// - /// - Parameters: - /// - buildData: A closure to build sections. - @available(*, deprecated, message: "This method will be removed next version owing to avoid ambiguity with new syntax using function builder.") - open func render(_ buildData: (inout [Section]) -> Void) { - var data = [Section]() - buildData(&data) - render(data) - } - open func render(@SectionsBuilder sections: () -> S) { render(sections().buildSections()) } diff --git a/Sources/Section.swift b/Sources/Section.swift index 61d3dc3..20b7cc6 100644 --- a/Sources/Section.swift +++ b/Sources/Section.swift @@ -63,19 +63,6 @@ public struct Section { public init(id: I, header: ViewNode? = nil, footer: ViewNode? = nil) { self.init(id: id, header: header, cells: [], footer: footer) } - - /// Create a section by given closure. - /// - /// - Parameters: - /// - id: An identifier to be wrapped. - /// - buildSection: A closure to build section. - @available(*, deprecated, message: "This method will be removed next version owing to avoid ambiguity with new syntax using function builder.") - @inlinable - public init(id: I, _ buildSection: (inout Section) -> Void) { - var section = Section(id: id) - buildSection(§ion) - self = section - } } public extension Section { diff --git a/Tests/RendererTests.swift b/Tests/RendererTests.swift index 9232dc2..2d20dc5 100644 --- a/Tests/RendererTests.swift +++ b/Tests/RendererTests.swift @@ -155,28 +155,4 @@ final class RendererTests: XCTestCase { XCTAssertEqual(renderer.updater.targetCapturedOnUpdates, target) XCTAssertEqual(renderer.updater.adapterCapturedOnUpdates, adapter) } - - @available(*, deprecated) - func testRenderWithBuilderClosure() { - let target = MockTarget() - let adapter = MockAdapter() - let renderer = Renderer( - adapter: adapter, - updater: MockUpdater() - ) - - let data = [ - Section(id: TestID.a), - Section(id: TestID.b), - Section(id: TestID.c), - Section(id: TestID.d) - ] - - renderer.target = target - renderer.render { $0 = data } - - XCTAssertEqual(renderer.adapter.data.count, 4) - XCTAssertEqual(renderer.updater.targetCapturedOnUpdates, target) - XCTAssertEqual(renderer.updater.adapterCapturedOnUpdates, adapter) - } } diff --git a/Tests/SectionTests.swift b/Tests/SectionTests.swift index 9006a9f..d8155b3 100644 --- a/Tests/SectionTests.swift +++ b/Tests/SectionTests.swift @@ -33,24 +33,6 @@ final class SectionTests: XCTestCase { XCTAssertEqual(section.cells.count, 2) } - @available(*, deprecated) - func testInitWithBuilderClosure() { - let section = Section(id: TestID.a) { section in - section.header = ViewNode(A.Component()) - section.cells = [ - CellNode(MockIdentifiableComponent(id: TestID.a)), - CellNode(MockIdentifiableComponent(id: TestID.b)), - CellNode(MockIdentifiableComponent(id: TestID.c)) - ] - section.footer = ViewNode(A.Component()) - } - - XCTAssertEqual(section.id.base as? TestID, .a) - XCTAssertNotNil(section.header) - XCTAssertNotNil(section.footer) - XCTAssertEqual(section.cells.count, 3) - } - func testContentEquatableConformance() { let section1 = Section( id: TestID.a, From e0660567cc94e1775516a48bc65c75f986c6a3c7 Mon Sep 17 00:00:00 2001 From: Ryo Aoyama Date: Thu, 5 Sep 2019 04:31:59 +0900 Subject: [PATCH 03/21] Update playground --- Carbon.playground/Contents.swift | 46 +++++++++++++------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/Carbon.playground/Contents.swift b/Carbon.playground/Contents.swift index 08dfded..a81fdca 100644 --- a/Carbon.playground/Contents.swift +++ b/Carbon.playground/Contents.swift @@ -12,15 +12,17 @@ import PlaygroundSupport // Setup -let frame = CGRect(x: 0, y: 0, width: 320, height: 480) +let frame = CGRect(x: 0, y: 0, width: 375, height: 812) let tableView = UITableView(frame: frame, style: .grouped) +tableView.estimatedSectionHeaderHeight = 44 +tableView.estimatedSectionFooterHeight = 44 PlaygroundPage.current.needsIndefiniteExecution = true PlaygroundPage.current.liveView = tableView // Define component -struct Label: Component, Equatable { +struct Label: Component { var text: String func renderContent() -> UILabel { @@ -30,10 +32,6 @@ struct Label: Component, Equatable { func render(in content: UILabel) { content.text = text } - - func referenceSize(in bounds: CGRect) -> CGSize? { - return CGSize(width: bounds.width, height: 44) - } } // Create renderer @@ -47,26 +45,20 @@ renderer.target = tableView // Render -renderer.render( +renderer.render { + Section(id: 0) { + Label(text: "Cell 1").identified(by: \.text) + Label(text: "Cell 2").identified(by: \.text) + Label(text: "Cell 3").identified(by: \.text) + Label(text: "Cell 4").identified(by: \.text) + } + Section( id: 1, - header: ViewNode(Label(text: "Header 1")), - cells: [ - CellNode(id: 1, Label(text: "Cell 1")), - CellNode(id: 2, Label(text: "Cell 2")), - CellNode(id: 3, Label(text: "Cell 3")), - CellNode(id: 4, Label(text: "Cell 4")) - ], - footer: ViewNode(Label(text: "Footer 1")) - ), - Section( - id: 2, - header: ViewNode(Label(text: "Header 2")), - cells: [ - CellNode(id: 5, Label(text: "Cell 5")), - CellNode(id: 6, Label(text: "Cell 6")), - CellNode(id: 7, Label(text: "Cell 7")) - ], - footer: ViewNode(Label(text: "Footer 2")) - ) -) + header: Label(text: "Header 1"), + footer: Label(text: "Footer 1"), + cells: { + Label(text: "Cell 5").identified(by: \.text) + Label(text: "Cell 6").identified(by: \.text) + }) +} From f8229252fbdd02537410d895bd13b8a1af785db2 Mon Sep 17 00:00:00 2001 From: Ryo Aoyama Date: Thu, 5 Sep 2019 04:43:34 +0900 Subject: [PATCH 04/21] Update README --- README.md | 84 ++++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 6c585be..d18876d 100644 --- a/README.md +++ b/README.md @@ -44,18 +44,17 @@ Our goal is similar to [Instagram/IGListKit](https://github.com/Instagram/IGList ```swift -renderer.render( - Section( - id: ID.greet, - header: ViewNode(Header(title: "GREET")), - cells: [ - CellNode(HelloMessage(name: "Vincent")), - CellNode(HelloMessage(name: "Jules")), - CellNode(HelloMessage(name: "Butch")) - ], - footer: ViewNode(Footer(text: "💡 Tap anywhere")) - ) -) +renderer.render { + Header(title: "GREET") + .identified(by: \.title) + + HelloMessage(name: "Vincent") + HelloMessage(name: "Jules") + HelloMessage(name: "Butch") + + Footer(text: "💡 Tap anywhere") + .identified(by: \.title) +} ``` --- @@ -157,24 +156,21 @@ This also needs to specify `id` for identify from among multiple sections, then - section moves - section updates -```swift -let emptySection = Section(id: 0) -``` - ```swift let showsHelloMia: Bool = ... let section = Section( id: "hello", - header: ViewNode(HelloMessage(name: "Vincent")), - cells: [ - CellNode(HelloMessage(name: "Jules")), - CellNode(HelloMessage(name: "Butch")), - !showsHelloMia - ? nil - : CellNode(HelloMessage(name: "Mia")) - ], - footer: ViewNode(HelloMessage(name: "Marsellus")) + header: Header(title: "GREET"), + footer: Footer(text: "Greeting from Carbon"), + cells: { + HelloMessage(name: "Jules") + HelloMessage(name: "Butch") + + if showsHelloMia { + HelloMessage(name: "Mia") + } + } ) ``` @@ -224,27 +220,25 @@ override func viewDidLoad() { ```swift let showsBottomSection: Bool = ... -renderer.render( - Section( - id: "top section", - cells: [ - CellNode(HelloMessage(name: "Vincent")), - CellNode(HelloMessage(name: "Jules")), - CellNode(HelloMessage(name: "Butch")) - ] - ), - !showsBottomSection - ? nil - : Section( - id: "bottom section", - header: ViewNode(HelloMessage(name: "Pumpkin")), - cells: [ - CellNode(HelloMessage(name: "Marsellus")), - CellNode(HelloMessage(name: "Mia")) - ], - footer: ViewNode(HelloMessage(name: "Honey Bunny")) +renderer.render { + Section(id: "top") { + HelloMessage(name: "Vincent") + HelloMessage(name: "Jules") + HelloMessage(name: "Butch") + } + + if showsBottomSection { + Section( + id: "bottom", + header: Header(title: "GREET"), + footer: Footer(text: "Greeting from Carbon"), + cells: { + HelloMessage(name: "Marsellus") + HelloMessage(name: "Mia") + } ) -) + } +} ```

From 05cca94e60005f54d4d80de738deab7ed2f07216 Mon Sep 17 00:00:00 2001 From: Ryo Aoyama Date: Thu, 5 Sep 2019 04:48:50 +0900 Subject: [PATCH 05/21] Update example --- Examples/Example-iOS/Sources/Common/Extensions.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Examples/Example-iOS/Sources/Common/Extensions.swift b/Examples/Example-iOS/Sources/Common/Extensions.swift index 3ff7f40..469d6f6 100644 --- a/Examples/Example-iOS/Sources/Common/Extensions.swift +++ b/Examples/Example-iOS/Sources/Common/Extensions.swift @@ -20,6 +20,12 @@ extension UITableView { } } +extension UICollectionView { + open override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } +} + extension UINavigationController { open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return topViewController?.supportedInterfaceOrientations ?? .portrait From de478dd8f1dd6a0a7c3a98872c9089b42aad4f13 Mon Sep 17 00:00:00 2001 From: Ryo Aoyama Date: Thu, 5 Sep 2019 17:31:26 +0900 Subject: [PATCH 06/21] Add init with closure building cells to SectionGroup --- Sources/FunctionBuilder/CellGroup.swift | 6 +++--- Sources/FunctionBuilder/SectionGroup.swift | 24 +++++++++++++++++++--- Sources/Renderer.swift | 11 +++------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Sources/FunctionBuilder/CellGroup.swift b/Sources/FunctionBuilder/CellGroup.swift index 8ac7b8d..126ea0b 100644 --- a/Sources/FunctionBuilder/CellGroup.swift +++ b/Sources/FunctionBuilder/CellGroup.swift @@ -1,13 +1,13 @@ public struct CellGroup: CellsBuildable { private var _buildCells: () -> [CellNode] - public init(@CellsBuilder _ cells: @escaping () -> C) { + public init(@CellsBuilder cells: () -> C) { _buildCells = cells().buildCells } - public init(of sequence: Seq, cell: @escaping (Seq.Element) -> C) { + public init(of data: Data, cell: @escaping (Data.Element) -> C) { _buildCells = { - sequence.flatMap { element in + data.flatMap { element in cell(element).buildCells() } } diff --git a/Sources/FunctionBuilder/SectionGroup.swift b/Sources/FunctionBuilder/SectionGroup.swift index f5dcdf0..4014fa1 100644 --- a/Sources/FunctionBuilder/SectionGroup.swift +++ b/Sources/FunctionBuilder/SectionGroup.swift @@ -1,19 +1,37 @@ public struct SectionGroup: SectionsBuildable { private var _buildSections: () -> [Section] - public init(@SectionsBuilder _ sections: @escaping () -> S) { + public init(@SectionsBuilder sections: () -> S) { _buildSections = sections().buildSections } - public init(of sequence: Seq, section: @escaping (Seq.Element) -> S) { + public init(@CellsBuilder cells: () -> C) { + let cells = cells() + + self.init { + Section(id: UniqueIdentifier()) { + cells + } + } + } + + public init(of data: Data, section: @escaping (Data.Element) -> S) { _buildSections = { - sequence.flatMap { element in + data.flatMap { element in section(element).buildSections() } } } + public init(of data: Data, cell: @escaping (Data.Element) -> C) { + self.init { + CellGroup(of: data, cell: cell) + } + } + public func buildSections() -> [Section] { _buildSections() } } + +private struct UniqueIdentifier: Hashable {} diff --git a/Sources/Renderer.swift b/Sources/Renderer.swift index bd17084..2a9d4de 100644 --- a/Sources/Renderer.swift +++ b/Sources/Renderer.swift @@ -103,13 +103,8 @@ open class Renderer { } open func render(@CellsBuilder cells: () -> C) { - render( - Section( - id: UniqueIdentifier(), - cells: cells().buildCells() - ) - ) + render { + SectionGroup(cells: cells) + } } } - -private struct UniqueIdentifier: Hashable {} From bf9e3bf5be4cbd5796ee50568c786571de850237 Mon Sep 17 00:00:00 2001 From: Ryo Aoyama Date: Thu, 5 Sep 2019 18:20:08 +0900 Subject: [PATCH 07/21] Refactor --- README.md | 4 ++-- Sources/FunctionBuilder/CellGroup.swift | 14 +++++------- Sources/FunctionBuilder/SectionGroup.swift | 26 +++++++++------------- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d18876d..603492f 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,8 @@ renderer.render { HelloMessage(name: "Jules") HelloMessage(name: "Butch") - Footer(text: "💡 Tap anywhere") - .identified(by: \.title) + Footer(text: "👋 Greeting from Carbon") + .identified(by: \.text) } ``` diff --git a/Sources/FunctionBuilder/CellGroup.swift b/Sources/FunctionBuilder/CellGroup.swift index 126ea0b..f72ef71 100644 --- a/Sources/FunctionBuilder/CellGroup.swift +++ b/Sources/FunctionBuilder/CellGroup.swift @@ -1,19 +1,17 @@ public struct CellGroup: CellsBuildable { - private var _buildCells: () -> [CellNode] + private var cells: [CellNode] public init(@CellsBuilder cells: () -> C) { - _buildCells = cells().buildCells + self.cells = cells().buildCells() } - public init(of data: Data, cell: @escaping (Data.Element) -> C) { - _buildCells = { - data.flatMap { element in - cell(element).buildCells() - } + public init(of data: Data, cell: (Data.Element) -> C) { + cells = data.flatMap { element in + cell(element).buildCells() } } public func buildCells() -> [CellNode] { - _buildCells() + cells } } diff --git a/Sources/FunctionBuilder/SectionGroup.swift b/Sources/FunctionBuilder/SectionGroup.swift index 4014fa1..e2f69bc 100644 --- a/Sources/FunctionBuilder/SectionGroup.swift +++ b/Sources/FunctionBuilder/SectionGroup.swift @@ -1,36 +1,30 @@ public struct SectionGroup: SectionsBuildable { - private var _buildSections: () -> [Section] + private var sections: [Section] public init(@SectionsBuilder sections: () -> S) { - _buildSections = sections().buildSections + self.sections = sections().buildSections() } public init(@CellsBuilder cells: () -> C) { - let cells = cells() - - self.init { - Section(id: UniqueIdentifier()) { - cells - } - } + sections = [ + Section(id: UniqueIdentifier(), cells: cells) + ] } - public init(of data: Data, section: @escaping (Data.Element) -> S) { - _buildSections = { - data.flatMap { element in - section(element).buildSections() - } + public init(of data: Data, section: (Data.Element) -> S) { + sections = data.flatMap { element in + section(element).buildSections() } } - public init(of data: Data, cell: @escaping (Data.Element) -> C) { + public init(of data: Data, cell: (Data.Element) -> C) { self.init { CellGroup(of: data, cell: cell) } } public func buildSections() -> [Section] { - _buildSections() + sections } } From 5cd5179890d7d35fa698b21356ac0bd6412a157b Mon Sep 17 00:00:00 2001 From: Ryo Aoyama Date: Fri, 6 Sep 2019 03:14:19 +0900 Subject: [PATCH 08/21] Update example app --- .../Example-iOS.xcodeproj/project.pbxproj | 4 +-- Examples/Example-iOS/Podfile.lock | 4 +-- .../Example-iOS/Sources/AppDelegate.swift | 5 ++-- .../Assets.xcassets/Color/Contents.json | 6 ---- .../Color/primaryBlack.colorset/Contents.json | 20 ------------- .../Color/primaryGreen.colorset/Contents.json | 20 ------------- .../Color/primaryWhite.colorset/Contents.json | 20 ------------- .../secondaryBlack.colorset/Contents.json | 20 ------------- .../Base.lproj/LaunchScreen.storyboard | 16 +++------- .../Sources/Common/Extensions.swift | 14 --------- .../Sources/Common/FooterContent.xib | 12 -------- .../Sources/Common/HeaderContent.xib | 13 +-------- .../Sources/Emoji/EmojiLabelContent.xib | 18 ++++-------- .../Sources/Emoji/EmojiViewController.xib | 24 ++++----------- .../Sources/Form/FormDatePicker.swift | 2 +- .../Sources/Form/FormDatePickerContent.xib | 21 ++++---------- .../Example-iOS/Sources/Form/FormLabel.swift | 2 +- .../Sources/Form/FormLabelContent.xib | 21 +++----------- .../Sources/Form/FormSwitchContent.xib | 25 ++++------------ .../Sources/Form/FormTextFieldContent.xib | 21 +++----------- .../Sources/Form/FormTextPicker.swift | 2 +- .../Sources/Form/FormTextPickerContent.xib | 16 +++------- .../Sources/Form/FormTextViewContent.xib | 21 ++++---------- .../Sources/Form/FormViewController.swift | 14 ++++----- .../Sources/Form/FormViewController.xib | 11 ++----- .../Sources/Hello/HelloMessageContent.xib | 12 -------- .../Sources/Hello/HelloViewController.swift | 2 +- .../Sources/Hello/HelloViewController.xib | 11 ++----- Examples/Example-iOS/Sources/Info.plist | 4 +-- .../Sources/Kyoto/KyotoImageContent.xib | 23 ++++----------- .../Sources/Kyoto/KyotoLicense.swift | 2 +- .../Sources/Kyoto/KyotoLicenseContent.xib | 13 +-------- .../Sources/Kyoto/KyotoTopContent.xib | 18 ++---------- .../Sources/Kyoto/KyotoViewController.xib | 11 ++----- .../Sources/Pangram/PangramLabel.swift | 4 +-- .../Sources/Pangram/PangramViewController.xib | 22 ++++---------- .../Sources/Todo/TodoEmptyContent.xib | 12 -------- .../Sources/Todo/TodoTextContent.xib | 20 ++----------- .../Sources/Todo/TodoViewController.swift | 1 + .../Sources/Todo/TodoViewController.xib | 29 ++++--------------- .../Example-iOS/Sources/Top/HomeItem.swift | 2 +- .../Sources/Top/HomeItemContent.xib | 15 +--------- .../Sources/Top/HomeViewController.xib | 10 ++----- Sources/Section.swift | 10 +++---- 44 files changed, 102 insertions(+), 471 deletions(-) delete mode 100644 Examples/Example-iOS/Sources/Assets.xcassets/Color/Contents.json delete mode 100644 Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryBlack.colorset/Contents.json delete mode 100644 Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryGreen.colorset/Contents.json delete mode 100644 Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryWhite.colorset/Contents.json delete mode 100644 Examples/Example-iOS/Sources/Assets.xcassets/Color/secondaryBlack.colorset/Contents.json diff --git a/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj b/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj index 2f7710c..3cbeb91 100644 --- a/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj +++ b/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj @@ -637,7 +637,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -661,7 +661,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Examples/Example-iOS/Podfile.lock b/Examples/Example-iOS/Podfile.lock index 8da5b1d..689d138 100644 --- a/Examples/Example-iOS/Podfile.lock +++ b/Examples/Example-iOS/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Carbon (1.0.0-rc.1): + - Carbon (1.0.0-rc.2): - DifferenceKit/Core (~> 1.1) - DifferenceKit/Core (1.1.3) - MagazineLayout (1.5.0) @@ -21,7 +21,7 @@ EXTERNAL SOURCES: :path: "../../" SPEC CHECKSUMS: - Carbon: 66addbbb03c1433ec0bcdc4317e3640862a08f71 + Carbon: ce5ab57b72c04aa3e92f1ac3a9861bdbfec5cdb4 DifferenceKit: 5018791b6c1fc839921a3c171a0a539ace6ea60c MagazineLayout: fe41dc2cf1923e3bf377cd8d906a4455a0bf9c83 SwipeCellKit: 935ca28c187ec6e1ffb2b578cf8ddca842bfdcbb diff --git a/Examples/Example-iOS/Sources/AppDelegate.swift b/Examples/Example-iOS/Sources/AppDelegate.swift index 3d230c2..396a912 100644 --- a/Examples/Example-iOS/Sources/AppDelegate.swift +++ b/Examples/Example-iOS/Sources/AppDelegate.swift @@ -18,11 +18,10 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func configureUIAppearance() { let appearance = UINavigationBar.appearance() let titleTextAttributes: [NSAttributedString.Key: Any] = [ - .foregroundColor: UIColor.primaryWhite + .foregroundColor: UIColor.label ] - appearance.tintColor = .primaryWhite - appearance.setBackgroundImage(UIColor.primaryBlack.image(), for: .default) + appearance.tintColor = .label appearance.prefersLargeTitles = true appearance.isTranslucent = true appearance.titleTextAttributes = titleTextAttributes diff --git a/Examples/Example-iOS/Sources/Assets.xcassets/Color/Contents.json b/Examples/Example-iOS/Sources/Assets.xcassets/Color/Contents.json deleted file mode 100644 index da4a164..0000000 --- a/Examples/Example-iOS/Sources/Assets.xcassets/Color/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryBlack.colorset/Contents.json b/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryBlack.colorset/Contents.json deleted file mode 100644 index a36cc53..0000000 --- a/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryBlack.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0x19", - "alpha" : "1.000", - "blue" : "0x21", - "green" : "0x1E" - } - } - } - ] -} \ No newline at end of file diff --git a/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryGreen.colorset/Contents.json b/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryGreen.colorset/Contents.json deleted file mode 100644 index 1ee5d79..0000000 --- a/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryGreen.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0x6A", - "alpha" : "1.000", - "blue" : "0x16", - "green" : "0xC7" - } - } - } - ] -} \ No newline at end of file diff --git a/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryWhite.colorset/Contents.json b/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryWhite.colorset/Contents.json deleted file mode 100644 index c2a1cc2..0000000 --- a/Examples/Example-iOS/Sources/Assets.xcassets/Color/primaryWhite.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0xFF", - "alpha" : "1.000", - "blue" : "0xFF", - "green" : "0xFF" - } - } - } - ] -} \ No newline at end of file diff --git a/Examples/Example-iOS/Sources/Assets.xcassets/Color/secondaryBlack.colorset/Contents.json b/Examples/Example-iOS/Sources/Assets.xcassets/Color/secondaryBlack.colorset/Contents.json deleted file mode 100644 index f4744e4..0000000 --- a/Examples/Example-iOS/Sources/Assets.xcassets/Color/secondaryBlack.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0x25", - "alpha" : "1.000", - "blue" : "0x2D", - "green" : "0x2A" - } - } - } - ] -} \ No newline at end of file diff --git a/Examples/Example-iOS/Sources/Base.lproj/LaunchScreen.storyboard b/Examples/Example-iOS/Sources/Base.lproj/LaunchScreen.storyboard index 87fb609..95e7ec4 100644 --- a/Examples/Example-iOS/Sources/Base.lproj/LaunchScreen.storyboard +++ b/Examples/Example-iOS/Sources/Base.lproj/LaunchScreen.storyboard @@ -1,11 +1,8 @@ - - - - + + - - + @@ -17,7 +14,7 @@ - + @@ -26,9 +23,4 @@ - - - - - diff --git a/Examples/Example-iOS/Sources/Common/Extensions.swift b/Examples/Example-iOS/Sources/Common/Extensions.swift index 469d6f6..334acf3 100644 --- a/Examples/Example-iOS/Sources/Common/Extensions.swift +++ b/Examples/Example-iOS/Sources/Common/Extensions.swift @@ -1,19 +1,5 @@ import UIKit -extension UIColor { - static let primaryBlack = UIColor(named: "primaryBlack")! - static let secondaryBlack = UIColor(named: "secondaryBlack")! - static let primaryWhite = UIColor(named: "primaryWhite")! - static let primaryGreen = UIColor(named: "primaryGreen")! - - func image(with size: CGSize = CGSize(width: 1 / UIScreen.main.scale, height: 1 / UIScreen.main.scale)) -> UIImage { - return UIGraphicsImageRenderer(size: size).image { context in - context.cgContext.setFillColor(cgColor) - context.fill(CGRect(origin: .zero, size: size)) - } - } -} - extension UITableView { open override func touchesShouldCancel(in view: UIView) -> Bool { return true diff --git a/Examples/Example-iOS/Sources/Common/FooterContent.xib b/Examples/Example-iOS/Sources/Common/FooterContent.xib index c206102..dabb840 100644 --- a/Examples/Example-iOS/Sources/Common/FooterContent.xib +++ b/Examples/Example-iOS/Sources/Common/FooterContent.xib @@ -2,9 +2,7 @@ - - @@ -18,11 +16,9 @@ - @@ -36,12 +32,4 @@ - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Common/HeaderContent.xib b/Examples/Example-iOS/Sources/Common/HeaderContent.xib index a27c7e7..be68ffc 100644 --- a/Examples/Example-iOS/Sources/Common/HeaderContent.xib +++ b/Examples/Example-iOS/Sources/Common/HeaderContent.xib @@ -2,9 +2,7 @@ - - @@ -18,11 +16,10 @@ - @@ -36,12 +33,4 @@ - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Emoji/EmojiLabelContent.xib b/Examples/Example-iOS/Sources/Emoji/EmojiLabelContent.xib index 24223b9..4872cbe 100644 --- a/Examples/Example-iOS/Sources/Emoji/EmojiLabelContent.xib +++ b/Examples/Example-iOS/Sources/Emoji/EmojiLabelContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -19,11 +16,11 @@ - + @@ -38,9 +35,4 @@ - - - - - diff --git a/Examples/Example-iOS/Sources/Emoji/EmojiViewController.xib b/Examples/Example-iOS/Sources/Emoji/EmojiViewController.xib index 3de5dfb..b2d47d8 100644 --- a/Examples/Example-iOS/Sources/Emoji/EmojiViewController.xib +++ b/Examples/Example-iOS/Sources/Emoji/EmojiViewController.xib @@ -2,9 +2,7 @@ - - @@ -23,7 +21,7 @@ - + @@ -33,26 +31,27 @@ + - + - + - + - + @@ -66,15 +65,4 @@ - - - - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Form/FormDatePicker.swift b/Examples/Example-iOS/Sources/Form/FormDatePicker.swift index 486b01e..f97ec74 100644 --- a/Examples/Example-iOS/Sources/Form/FormDatePicker.swift +++ b/Examples/Example-iOS/Sources/Form/FormDatePicker.swift @@ -27,7 +27,7 @@ final class FormDatePickerContent: UIView, NibLoadable { override func awakeFromNib() { super.awakeFromNib() - datePicker.setValue(UIColor.primaryWhite, forKeyPath: "textColor") + datePicker.setValue(UIColor.label, forKeyPath: "textColor") datePicker.addTarget(self, action: #selector(selected), for: .valueChanged) } diff --git a/Examples/Example-iOS/Sources/Form/FormDatePickerContent.xib b/Examples/Example-iOS/Sources/Form/FormDatePickerContent.xib index 864e321..9561545 100644 --- a/Examples/Example-iOS/Sources/Form/FormDatePickerContent.xib +++ b/Examples/Example-iOS/Sources/Form/FormDatePickerContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -16,17 +13,14 @@ - + - - - - + @@ -41,9 +35,4 @@ - - - - - diff --git a/Examples/Example-iOS/Sources/Form/FormLabel.swift b/Examples/Example-iOS/Sources/Form/FormLabel.swift index 8b3c568..1b681ba 100644 --- a/Examples/Example-iOS/Sources/Form/FormLabel.swift +++ b/Examples/Example-iOS/Sources/Form/FormLabel.swift @@ -29,7 +29,7 @@ final class FormLabelContent: UIControl, NibLoadable { override var isHighlighted: Bool { didSet { - backgroundColor = isHighlighted ? .primaryBlack : .secondaryBlack + backgroundColor = isHighlighted ? .systemGray4 : .secondarySystemBackground } } diff --git a/Examples/Example-iOS/Sources/Form/FormLabelContent.xib b/Examples/Example-iOS/Sources/Form/FormLabelContent.xib index de6cf26..f018fb4 100644 --- a/Examples/Example-iOS/Sources/Form/FormLabelContent.xib +++ b/Examples/Example-iOS/Sources/Form/FormLabelContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -22,7 +19,6 @@ - - + @@ -54,12 +49,4 @@ - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Form/FormSwitchContent.xib b/Examples/Example-iOS/Sources/Form/FormSwitchContent.xib index 338b330..32a69dd 100644 --- a/Examples/Example-iOS/Sources/Form/FormSwitchContent.xib +++ b/Examples/Example-iOS/Sources/Form/FormSwitchContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -22,15 +19,14 @@ - - + - + @@ -49,15 +45,4 @@ - - - - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Form/FormTextFieldContent.xib b/Examples/Example-iOS/Sources/Form/FormTextFieldContent.xib index 95e3fc9..272b080 100644 --- a/Examples/Example-iOS/Sources/Form/FormTextFieldContent.xib +++ b/Examples/Example-iOS/Sources/Form/FormTextFieldContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -22,7 +19,6 @@ - @@ -30,12 +26,11 @@ - - + @@ -53,12 +48,4 @@ - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Form/FormTextPicker.swift b/Examples/Example-iOS/Sources/Form/FormTextPicker.swift index 9a525e4..55d873d 100644 --- a/Examples/Example-iOS/Sources/Form/FormTextPicker.swift +++ b/Examples/Example-iOS/Sources/Form/FormTextPicker.swift @@ -43,7 +43,7 @@ final class FormTextPickerContent: UIView, NibLoadable, UIPickerViewDelegate, UI } func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - return NSAttributedString(string: texts[row], attributes: [.foregroundColor: UIColor.white]) + return NSAttributedString(string: texts[row], attributes: [.foregroundColor: UIColor.label]) } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { diff --git a/Examples/Example-iOS/Sources/Form/FormTextPickerContent.xib b/Examples/Example-iOS/Sources/Form/FormTextPickerContent.xib index 749e9f3..6fb6ea5 100644 --- a/Examples/Example-iOS/Sources/Form/FormTextPickerContent.xib +++ b/Examples/Example-iOS/Sources/Form/FormTextPickerContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -23,7 +20,7 @@ - + @@ -38,9 +35,4 @@ - - - - - diff --git a/Examples/Example-iOS/Sources/Form/FormTextViewContent.xib b/Examples/Example-iOS/Sources/Form/FormTextViewContent.xib index 2472a5e..b3b4aae 100644 --- a/Examples/Example-iOS/Sources/Form/FormTextViewContent.xib +++ b/Examples/Example-iOS/Sources/Form/FormTextViewContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -22,12 +19,12 @@ Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. - + - + @@ -42,12 +39,4 @@ - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Form/FormViewController.swift b/Examples/Example-iOS/Sources/Form/FormViewController.swift index df28181..7817e3a 100644 --- a/Examples/Example-iOS/Sources/Form/FormViewController.swift +++ b/Examples/Example-iOS/Sources/Form/FormViewController.swift @@ -3,11 +3,10 @@ import Carbon final class FormViewController: UIViewController { enum ID { - case about - case note - case detail case genderPicker case birthdayPicker + case note + case detail } enum Gender: String, CaseIterable { @@ -46,19 +45,16 @@ final class FormViewController: UIViewController { title = "Profile Form" NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) - tableView.contentInset.bottom = 150 renderer.target = tableView renderer.updater.deleteRowsAnimation = .middle renderer.updater.insertRowsAnimation = .middle - renderer.updater.insertSectionsAnimation = .top - renderer.updater.deleteSectionsAnimation = .top render() } func render() { renderer.render { - Section(id: ID.about) { + CellGroup { Header(title: "ABOUT") .identified(by: \.title) @@ -89,7 +85,7 @@ final class FormViewController: UIViewController { } } - Section(id: ID.note) { + CellGroup { Header(title: "NOTE") .identified(by: \.title) @@ -99,7 +95,7 @@ final class FormViewController: UIViewController { .identified(by: ID.note) } - Section(id: ID.detail) { + CellGroup { Header(title: "DETAILS") .identified(by: \.title) diff --git a/Examples/Example-iOS/Sources/Form/FormViewController.xib b/Examples/Example-iOS/Sources/Form/FormViewController.xib index 7a835c8..ff56268 100644 --- a/Examples/Example-iOS/Sources/Form/FormViewController.xib +++ b/Examples/Example-iOS/Sources/Form/FormViewController.xib @@ -2,9 +2,7 @@ - - @@ -22,10 +20,10 @@ - + - + @@ -36,9 +34,4 @@ - - - - - diff --git a/Examples/Example-iOS/Sources/Hello/HelloMessageContent.xib b/Examples/Example-iOS/Sources/Hello/HelloMessageContent.xib index b70b1a3..b9ab7cf 100644 --- a/Examples/Example-iOS/Sources/Hello/HelloMessageContent.xib +++ b/Examples/Example-iOS/Sources/Hello/HelloMessageContent.xib @@ -2,9 +2,7 @@ - - @@ -21,11 +19,9 @@ - - @@ -40,12 +36,4 @@ - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Hello/HelloViewController.swift b/Examples/Example-iOS/Sources/Hello/HelloViewController.swift index d2868b1..e2b112f 100644 --- a/Examples/Example-iOS/Sources/Hello/HelloViewController.swift +++ b/Examples/Example-iOS/Sources/Hello/HelloViewController.swift @@ -38,7 +38,7 @@ final class HelloViewController: UIViewController { HelloMessage(name: "Butch") } - Footer(text: "Tap anywhere 💡") + Footer(text: "👋 Greeting from Carbon") .identified(by: \.text) } } diff --git a/Examples/Example-iOS/Sources/Hello/HelloViewController.xib b/Examples/Example-iOS/Sources/Hello/HelloViewController.xib index 6528183..131ce2e 100644 --- a/Examples/Example-iOS/Sources/Hello/HelloViewController.xib +++ b/Examples/Example-iOS/Sources/Hello/HelloViewController.xib @@ -2,9 +2,7 @@ - - @@ -22,10 +20,10 @@ - + - + @@ -36,9 +34,4 @@ - - - - - diff --git a/Examples/Example-iOS/Sources/Info.plist b/Examples/Example-iOS/Sources/Info.plist index b0d647a..80cd605 100644 --- a/Examples/Example-iOS/Sources/Info.plist +++ b/Examples/Example-iOS/Sources/Info.plist @@ -26,8 +26,6 @@ armv7 - UIStatusBarStyle - UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -42,6 +40,6 @@ UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance - + diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoImageContent.xib b/Examples/Example-iOS/Sources/Kyoto/KyotoImageContent.xib index e3988c6..cfc7e89 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoImageContent.xib +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoImageContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -25,11 +22,10 @@ - + @@ -45,14 +41,7 @@ + - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoLicense.swift b/Examples/Example-iOS/Sources/Kyoto/KyotoLicense.swift index 0434d0e..50c1265 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoLicense.swift +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoLicense.swift @@ -16,7 +16,7 @@ struct KyotoLicense: Component { final class KyotoLicenseContent: UIControl, NibLoadable { override var isHighlighted: Bool { didSet { - alpha = isHighlighted ? 0.5 : 1 + backgroundColor = isHighlighted ? .systemGray4 : .clear } } diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoLicenseContent.xib b/Examples/Example-iOS/Sources/Kyoto/KyotoLicenseContent.xib index 669bebb..b3941df 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoLicenseContent.xib +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoLicenseContent.xib @@ -2,9 +2,7 @@ - - @@ -20,11 +18,10 @@ 📸 Photos are provided by Unsplash - + - @@ -36,12 +33,4 @@ by Unsplash - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoTopContent.xib b/Examples/Example-iOS/Sources/Kyoto/KyotoTopContent.xib index d8a29de..d0d694d 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoTopContent.xib +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoTopContent.xib @@ -1,11 +1,8 @@ - - - - + + - - + @@ -25,7 +22,6 @@ - @@ -59,11 +53,5 @@ It is designated as a National Special Historic Site, a National Special Landsca - - - - - - diff --git a/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.xib b/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.xib index a51badf..ea9b693 100644 --- a/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.xib +++ b/Examples/Example-iOS/Sources/Kyoto/KyotoViewController.xib @@ -2,9 +2,7 @@ - - @@ -22,7 +20,7 @@ - + @@ -31,7 +29,7 @@ - + @@ -42,9 +40,4 @@ - - - - - diff --git a/Examples/Example-iOS/Sources/Pangram/PangramLabel.swift b/Examples/Example-iOS/Sources/Pangram/PangramLabel.swift index c367008..0020b00 100644 --- a/Examples/Example-iOS/Sources/Pangram/PangramLabel.swift +++ b/Examples/Example-iOS/Sources/Pangram/PangramLabel.swift @@ -6,10 +6,10 @@ struct PangramLabel: IdentifiableComponent, Hashable { func renderContent() -> UILabel { let label = UILabel() - label.textColor = .primaryGreen + label.textColor = .systemGreen label.textAlignment = .center label.font = .boldSystemFont(ofSize: 20) - label.backgroundColor = UIColor.primaryWhite.withAlphaComponent(0.1) + label.backgroundColor = UIColor.systemGray6 return label } diff --git a/Examples/Example-iOS/Sources/Pangram/PangramViewController.xib b/Examples/Example-iOS/Sources/Pangram/PangramViewController.xib index b80ba38..2520fa4 100644 --- a/Examples/Example-iOS/Sources/Pangram/PangramViewController.xib +++ b/Examples/Example-iOS/Sources/Pangram/PangramViewController.xib @@ -2,9 +2,7 @@ - - @@ -23,7 +21,7 @@ - + @@ -33,19 +31,20 @@ + - + - + - + @@ -59,15 +58,4 @@ - - - - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Todo/TodoEmptyContent.xib b/Examples/Example-iOS/Sources/Todo/TodoEmptyContent.xib index e18f6c0..cff5218 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoEmptyContent.xib +++ b/Examples/Example-iOS/Sources/Todo/TodoEmptyContent.xib @@ -2,9 +2,7 @@ - - @@ -22,11 +20,9 @@ Let's add a new task from the button below 👇 - - @@ -40,12 +36,4 @@ from the button below 👇 - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Todo/TodoTextContent.xib b/Examples/Example-iOS/Sources/Todo/TodoTextContent.xib index f544479..e31ff7e 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoTextContent.xib +++ b/Examples/Example-iOS/Sources/Todo/TodoTextContent.xib @@ -2,9 +2,7 @@ - - @@ -24,10 +22,10 @@ - + - + - + @@ -59,15 +56,4 @@ - - - - - - - - - - - diff --git a/Examples/Example-iOS/Sources/Todo/TodoViewController.swift b/Examples/Example-iOS/Sources/Todo/TodoViewController.swift index b18a574..223636f 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoViewController.swift +++ b/Examples/Example-iOS/Sources/Todo/TodoViewController.swift @@ -139,6 +139,7 @@ final class SwipeCellKitTodoAdapter: UITableViewAdapter, SwipeTableViewCellDeleg let cell = super.tableView(tableView, cellForRowAt: indexPath) as! SwipeTableViewCell cell.delegate = self cell.selectionStyle = .none + cell.backgroundColor = .clear return cell } diff --git a/Examples/Example-iOS/Sources/Todo/TodoViewController.xib b/Examples/Example-iOS/Sources/Todo/TodoViewController.xib index 422a465..498dd1c 100644 --- a/Examples/Example-iOS/Sources/Todo/TodoViewController.xib +++ b/Examples/Example-iOS/Sources/Todo/TodoViewController.xib @@ -2,9 +2,7 @@ - - @@ -27,19 +25,19 @@ - +