From 2bd51e3ed5b9c68caa2bda992720f27c9c3a7214 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 16 Jan 2019 02:57:07 -0800 Subject: [PATCH 01/10] Abstract centered label, remove unnecessary button boilerplate, and add spacing view --- .../Source/Detail/View/DetailView.swift | 41 +++++-------------- .../Source/Extensions/Extensions.swift | 11 +++++ Application/Source/Home/View/HomeView.swift | 13 +----- .../Source/Selection/SelectionView.swift | 7 +--- 4 files changed, 25 insertions(+), 47 deletions(-) create mode 100644 Application/Source/Extensions/Extensions.swift diff --git a/Application/Source/Detail/View/DetailView.swift b/Application/Source/Detail/View/DetailView.swift index 10ca09a..7606ad2 100644 --- a/Application/Source/Detail/View/DetailView.swift +++ b/Application/Source/Detail/View/DetailView.swift @@ -3,44 +3,25 @@ import SnapKit class DetailView: UIView { - let title: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.numberOfLines = 0 - return label + let title = UILabel.centeredLabel() + let button = UIButton() + let selectionResult = UILabel.centeredLabel() + + private let spacingView: UIView = { + let view = UIView() + view.snp.makeConstraints { $0.height.equalTo(35) } + return view }() - let button: UIButton = { - let button = UIButton() - button.setTitleColor(.blue, for: .normal) - return button - }() - - let selectionResult: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.numberOfLines = 0 - return label - }() - - let contentsListTitle: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.numberOfLines = 0 - return label - }() - - let contentsButton: UIButton = { - let button = UIButton() - button.setTitleColor(.blue, for: .normal) - return button - }() + let contentsListTitle = UILabel.centeredLabel() + let contentsButton = UIButton() private(set) lazy var stackView: UIStackView = { let stackView = UIStackView(arrangedSubviews: [ title, button, selectionResult, + spacingView, contentsListTitle, contentsButton ]) diff --git a/Application/Source/Extensions/Extensions.swift b/Application/Source/Extensions/Extensions.swift new file mode 100644 index 0000000..c84dc62 --- /dev/null +++ b/Application/Source/Extensions/Extensions.swift @@ -0,0 +1,11 @@ +import UIKit + +extension UILabel { + + static func centeredLabel() -> UILabel { + let label = UILabel() + label.textAlignment = .center + label.numberOfLines = 0 + return label + } +} diff --git a/Application/Source/Home/View/HomeView.swift b/Application/Source/Home/View/HomeView.swift index 6cc8e70..4055ae7 100644 --- a/Application/Source/Home/View/HomeView.swift +++ b/Application/Source/Home/View/HomeView.swift @@ -3,12 +3,7 @@ import SnapKit class HomeView: UIView { - let label: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.numberOfLines = 0 - return label - }() + let label = UILabel.centeredLabel() let imageView: UIImageView = { let imageView = UIImageView() @@ -17,11 +12,7 @@ class HomeView: UIView { return imageView }() - let detailButton: UIButton = { - let button = UIButton() - button.setTitleColor(.blue, for: .normal) - return button - }() + let detailButton = UIButton() private(set) lazy var stackView: UIStackView = { let stackView = UIStackView(arrangedSubviews: [ diff --git a/Application/Source/Selection/SelectionView.swift b/Application/Source/Selection/SelectionView.swift index ff03719..cbaa67e 100644 --- a/Application/Source/Selection/SelectionView.swift +++ b/Application/Source/Selection/SelectionView.swift @@ -4,12 +4,7 @@ import SnapKit class SelectionView: UIView { let textField = UITextField() - - let submitButton: UIButton = { - let button = UIButton() - button.setTitleColor(.blue, for: .normal) - return button - }() + let submitButton = UIButton() private(set) lazy var stackView: UIStackView = { let stackView = UIStackView(arrangedSubviews: [ From 59aa29b48f3dba68027d1743923a12d01d03b604 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 16 Jan 2019 18:19:58 -0800 Subject: [PATCH 02/10] No longer excluding colors.txt from project in Xcode --- Project_Core.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Project_Core.yml b/Project_Core.yml index d413004..90db074 100644 --- a/Project_Core.yml +++ b/Project_Core.yml @@ -7,8 +7,6 @@ targets: - Core/Source - Core/Supporting Files - path: Core/Resources - excludes: - - "*.txt" dependencies: - target: Themer - target: Logger From 7fa1b21763be3c2d5642bc7400ba292ea11d2e3a Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 16 Jan 2019 18:20:41 -0800 Subject: [PATCH 03/10] Add alternate body text color --- .../Source/Detail/View/DetailViewControllerStyle.swift | 4 ++-- Core/Resources/Colors.txt | 2 ++ Core/Source/SwiftGen/Colors.swift | 6 ++++++ Core/Source/Themes/Styles/LabelStyle.swift | 9 +++++++++ Core/Source/Themes/Styles/TextFieldStyle.swift | 2 +- Core/Source/Themes/Themes.swift | 3 +++ 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Application/Source/Detail/View/DetailViewControllerStyle.swift b/Application/Source/Detail/View/DetailViewControllerStyle.swift index 7904783..a3938e0 100644 --- a/Application/Source/Detail/View/DetailViewControllerStyle.swift +++ b/Application/Source/Detail/View/DetailViewControllerStyle.swift @@ -6,14 +6,14 @@ struct DetailViewControllerStyle: Style { let theme: Theme let background: BackgroundViewStyle let title: LabelStyle - let selectionResult: LabelStyle + let selectionResult: AlternateLabelStyle let button: ButtonStyle init(theme: Theme) { self.theme = theme background = BackgroundViewStyle(theme: theme) title = LabelStyle(theme: theme) - selectionResult = LabelStyle(theme: theme) + selectionResult = AlternateLabelStyle(theme: theme) button = ButtonStyle(theme: theme) } diff --git a/Core/Resources/Colors.txt b/Core/Resources/Colors.txt index a042b07..171b045 100644 --- a/Core/Resources/Colors.txt +++ b/Core/Resources/Colors.txt @@ -1,6 +1,8 @@ LightContent : #000000 +LightContentAlt : #7b7b7b LightBackground : #FFFFFF LightActionColor : #11A4D6 DarkContent : #D3D3D3 +DarkContentAlt : #7b7b7b DarkBackground : #000000 DarkActionColor : #15B7ED diff --git a/Core/Source/SwiftGen/Colors.swift b/Core/Source/SwiftGen/Colors.swift index 36a2b48..78e2db4 100644 --- a/Core/Source/SwiftGen/Colors.swift +++ b/Core/Source/SwiftGen/Colors.swift @@ -28,6 +28,9 @@ public struct Color { /// /// Alpha: 100%
(0xd3d3d3ff) public static let darkContent = Color(rgbaValue: 0xd3d3d3ff) + /// + /// Alpha: 100%
(0x888888ff) + public static let darkContentAlt = Color(rgbaValue: 0x888888ff) /// /// Alpha: 100%
(0x11a4d6ff) public static let lightActionColor = Color(rgbaValue: 0x11a4d6ff) @@ -37,6 +40,9 @@ public struct Color { /// /// Alpha: 100%
(0x000000ff) public static let lightContent = Color(rgbaValue: 0x000000ff) + /// + /// Alpha: 100%
(0x888888ff) + public static let lightContentAlt = Color(rgbaValue: 0x888888ff) } // swiftlint:enable identifier_name line_length type_body_length diff --git a/Core/Source/Themes/Styles/LabelStyle.swift b/Core/Source/Themes/Styles/LabelStyle.swift index c38dd8a..85830ea 100644 --- a/Core/Source/Themes/Styles/LabelStyle.swift +++ b/Core/Source/Themes/Styles/LabelStyle.swift @@ -10,3 +10,12 @@ public struct LabelStyle: UILabelStyle { } } + +public struct AlternateLabelStyle: UILabelStyle { + + public let textColor: UIColor + + public init(theme: Theme) { + textColor = theme.color.alternateBodyText + } +} diff --git a/Core/Source/Themes/Styles/TextFieldStyle.swift b/Core/Source/Themes/Styles/TextFieldStyle.swift index 25b24e9..333a833 100644 --- a/Core/Source/Themes/Styles/TextFieldStyle.swift +++ b/Core/Source/Themes/Styles/TextFieldStyle.swift @@ -6,7 +6,7 @@ public struct TextFieldStyle: UITextFieldStyle { public let textColor: UIColor public init(theme: Theme) { - textColor = theme.color.bodyText + textColor = theme.color.alternateBodyText } } diff --git a/Core/Source/Themes/Themes.swift b/Core/Source/Themes/Themes.swift index fc3709b..19544ee 100644 --- a/Core/Source/Themes/Themes.swift +++ b/Core/Source/Themes/Themes.swift @@ -10,6 +10,7 @@ public extension Theme { switch self { case .light: return ColorSet( bodyText: Color.lightContent.color, + alternateBodyText: Color.lightContentAlt.color, inputText: Color.lightContent.color, actionColor: Color.lightActionColor.color, viewBackground: Color.lightBackground.color, @@ -17,6 +18,7 @@ public extension Theme { tabBarTint: nil) case .dark: return ColorSet( bodyText: Color.darkContent.color, + alternateBodyText: Color.darkContentAlt.color, inputText: Color.darkContent.color, actionColor: Color.darkActionColor.color, viewBackground: Color.darkBackground.color, @@ -44,6 +46,7 @@ public extension Theme { public struct ColorSet { public let bodyText: UIColor + public let alternateBodyText: UIColor public let inputText: UIColor public let actionColor: UIColor public let viewBackground: UIColor? From f1b289bec7a6c9bab62c62d2af450fc2e0ee9d2a Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 16 Jan 2019 22:00:07 -0800 Subject: [PATCH 04/10] Adding content / food enum for testing --- .../Source/Detail/View/DetailViewModel.swift | 7 ++++ Application/Source/Models/Content.swift | 42 +++++++++++++++++++ Application/Source/SwiftGen/Strings.swift | 11 +++++ .../Base.lproj/Localizable.strings | 6 +++ Core/Source/SwiftGen/Colors.swift | 12 +++--- 5 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 Application/Source/Models/Content.swift diff --git a/Application/Source/Detail/View/DetailViewModel.swift b/Application/Source/Detail/View/DetailViewModel.swift index 2bcdc49..456109e 100644 --- a/Application/Source/Detail/View/DetailViewModel.swift +++ b/Application/Source/Detail/View/DetailViewModel.swift @@ -25,6 +25,13 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { return .empty() } + let content: [Food] = [ + .beans, + .greens, + .potatoes, + .tomatoes + ] + private(set) lazy var presentSelection = makePresentSelection( withFactory: selectionFactory, defaultValue: { [weak self] in diff --git a/Application/Source/Models/Content.swift b/Application/Source/Models/Content.swift new file mode 100644 index 0000000..44ba1cc --- /dev/null +++ b/Application/Source/Models/Content.swift @@ -0,0 +1,42 @@ +import Foundation +import RxCocoa + +protocol Content { + associatedtype CategoryType + + var name: BehaviorRelay { get } + var category: CategoryType { get } +} + +/** + It's not a side project if you can't have some fun, right? + - [Original.](https://www.youtube.com/watch?v=amONEHAhLHY) + - [Crispy.](https://www.youtube.com/watch?v=1BC1G33-fNY) + */ +enum Food: Content { + case beans, greens, potatoes, tomatoes + + enum Category: Int { + case fruit, tuber, vegetable, legume + } + + var name: BehaviorRelay { + let value: String + switch self { + case .beans: value = L10n.Food.beans + case .greens: value = L10n.Food.greens + case .potatoes: value = L10n.Food.potatoes + case .tomatoes: value = L10n.Food.tomatoes + } + return BehaviorRelay(value: value) + } + + var category: Category { + switch self { + case .beans: return .legume + case .greens: return .vegetable + case .potatoes: return .tuber + case .tomatoes: return .fruit + } + } +} diff --git a/Application/Source/SwiftGen/Strings.swift b/Application/Source/SwiftGen/Strings.swift index c14a833..417cdd5 100644 --- a/Application/Source/SwiftGen/Strings.swift +++ b/Application/Source/SwiftGen/Strings.swift @@ -36,6 +36,17 @@ internal enum L10n { } } + internal enum Food { + /// Beans + internal static let beans = L10n.tr("Localizable", "food.beans") + /// Greens + internal static let greens = L10n.tr("Localizable", "food.greens") + /// Potatoes + internal static let potatoes = L10n.tr("Localizable", "food.potatoes") + /// Tomatoes + internal static let tomatoes = L10n.tr("Localizable", "food.tomatoes") + } + internal enum Home { /// It works! internal static let testText = L10n.tr("Localizable", "home.test_text") diff --git a/Application/Supporting Files/Base.lproj/Localizable.strings b/Application/Supporting Files/Base.lproj/Localizable.strings index df431bd..9d8c06e 100644 --- a/Application/Supporting Files/Base.lproj/Localizable.strings +++ b/Application/Supporting Files/Base.lproj/Localizable.strings @@ -30,3 +30,9 @@ /* The title of the tab bar item for the settings navigation flow. */ "settings_navigation.tab_bar_item.title" = "Settings"; + +/* Food enum names. */ +"food.beans" = "Beans"; +"food.greens" = "Greens"; +"food.potatoes" = "Potatoes"; +"food.tomatoes" = "Tomatoes"; diff --git a/Core/Source/SwiftGen/Colors.swift b/Core/Source/SwiftGen/Colors.swift index 78e2db4..53deadd 100644 --- a/Core/Source/SwiftGen/Colors.swift +++ b/Core/Source/SwiftGen/Colors.swift @@ -28,9 +28,9 @@ public struct Color { /// /// Alpha: 100%
(0xd3d3d3ff) public static let darkContent = Color(rgbaValue: 0xd3d3d3ff) - /// - /// Alpha: 100%
(0x888888ff) - public static let darkContentAlt = Color(rgbaValue: 0x888888ff) + /// + /// Alpha: 100%
(0x7b7b7bff) + public static let darkContentAlt = Color(rgbaValue: 0x7b7b7bff) /// /// Alpha: 100%
(0x11a4d6ff) public static let lightActionColor = Color(rgbaValue: 0x11a4d6ff) @@ -40,9 +40,9 @@ public struct Color { /// /// Alpha: 100%
(0x000000ff) public static let lightContent = Color(rgbaValue: 0x000000ff) - /// - /// Alpha: 100%
(0x888888ff) - public static let lightContentAlt = Color(rgbaValue: 0x888888ff) + /// + /// Alpha: 100%
(0x7b7b7bff) + public static let lightContentAlt = Color(rgbaValue: 0x7b7b7bff) } // swiftlint:enable identifier_name line_length type_body_length From 45a4791b8bfc76c629270a28bd5857df26edfd43 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Thu, 17 Jan 2019 14:48:13 -0800 Subject: [PATCH 05/10] Display contents in detail view --- Application/Source/Detail/View/DetailView.swift | 2 ++ .../Source/Detail/View/DetailViewController.swift | 13 +++++++++++++ .../Detail/View/DetailViewControllerStyle.swift | 11 +++++++---- .../Source/Detail/View/DetailViewModel.swift | 7 +------ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Application/Source/Detail/View/DetailView.swift b/Application/Source/Detail/View/DetailView.swift index 7606ad2..9d9879f 100644 --- a/Application/Source/Detail/View/DetailView.swift +++ b/Application/Source/Detail/View/DetailView.swift @@ -14,6 +14,7 @@ class DetailView: UIView { }() let contentsListTitle = UILabel.centeredLabel() + let contentsList = UILabel.centeredLabel() let contentsButton = UIButton() private(set) lazy var stackView: UIStackView = { @@ -23,6 +24,7 @@ class DetailView: UIView { selectionResult, spacingView, contentsListTitle, + contentsList, contentsButton ]) stackView.axis = .vertical diff --git a/Application/Source/Detail/View/DetailViewController.swift b/Application/Source/Detail/View/DetailViewController.swift index adaacdf..4ae1ff3 100644 --- a/Application/Source/Detail/View/DetailViewController.swift +++ b/Application/Source/Detail/View/DetailViewController.swift @@ -32,15 +32,28 @@ class DetailViewController: UIViewController, ViewController { viewModel.title .bind(to: detailView.title.rx.text) .disposed(by: disposeBag) + viewModel.presentSelectionTitle .bind(to: detailView.button.rx.title()) .disposed(by: disposeBag) + viewModel.selectionResult .bind(to: detailView.selectionResult.rx.text) .disposed(by: disposeBag) + viewModel.contentsListTitle .bind(to: detailView.contentsListTitle.rx.text) .disposed(by: disposeBag) + + viewModel.content.value + .map { $0.name } + .reduce(BehaviorRelay(value: "")) { + let combined = ($0.value == "") ? $1.value : $0.value + ", " + $1.value + return BehaviorRelay(value: combined) + } + .bind(to: detailView.contentsList.rx.text) + .disposed(by: disposeBag) + viewModel.contentsButtonTitle .bind(to: detailView.contentsButton.rx.title()) .disposed(by: disposeBag) diff --git a/Application/Source/Detail/View/DetailViewControllerStyle.swift b/Application/Source/Detail/View/DetailViewControllerStyle.swift index a3938e0..aa145d3 100644 --- a/Application/Source/Detail/View/DetailViewControllerStyle.swift +++ b/Application/Source/Detail/View/DetailViewControllerStyle.swift @@ -6,24 +6,27 @@ struct DetailViewControllerStyle: Style { let theme: Theme let background: BackgroundViewStyle let title: LabelStyle - let selectionResult: AlternateLabelStyle + let alternateCopy: AlternateLabelStyle let button: ButtonStyle init(theme: Theme) { self.theme = theme background = BackgroundViewStyle(theme: theme) title = LabelStyle(theme: theme) - selectionResult = AlternateLabelStyle(theme: theme) + alternateCopy = AlternateLabelStyle(theme: theme) button = ButtonStyle(theme: theme) } func apply(to styleable: DetailViewController) { let view = styleable.detailView - background.apply(to: view) + title.apply(to: view.title) title.apply(to: view.contentsListTitle) - selectionResult.apply(to: view.selectionResult) + + alternateCopy.apply(to: view.selectionResult) + alternateCopy.apply(to: view.contentsList) + button.apply(to: view.button) button.apply(to: view.contentsButton) diff --git a/Application/Source/Detail/View/DetailViewModel.swift b/Application/Source/Detail/View/DetailViewModel.swift index 456109e..a2629b2 100644 --- a/Application/Source/Detail/View/DetailViewModel.swift +++ b/Application/Source/Detail/View/DetailViewModel.swift @@ -25,12 +25,7 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { return .empty() } - let content: [Food] = [ - .beans, - .greens, - .potatoes, - .tomatoes - ] + let content: Property<[Food]> = Property([.beans, .greens, .potatoes, .tomatoes]) private(set) lazy var presentSelection = makePresentSelection( withFactory: selectionFactory, From 7936b5bd84d9020e4ef0e5ae8fc6d41fe67d143d Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Thu, 17 Jan 2019 15:52:28 -0800 Subject: [PATCH 06/10] Further abstract contents list creation and add unit test --- .../Source/Detail/View/DetailViewController.swift | 11 +---------- .../Source/Detail/View/DetailViewModel.swift | 11 +++++++++++ Application/Tests/DetailViewModelSpec.swift | 13 ++++++++++++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Application/Source/Detail/View/DetailViewController.swift b/Application/Source/Detail/View/DetailViewController.swift index 4ae1ff3..23b3b24 100644 --- a/Application/Source/Detail/View/DetailViewController.swift +++ b/Application/Source/Detail/View/DetailViewController.swift @@ -32,11 +32,9 @@ class DetailViewController: UIViewController, ViewController { viewModel.title .bind(to: detailView.title.rx.text) .disposed(by: disposeBag) - viewModel.presentSelectionTitle .bind(to: detailView.button.rx.title()) .disposed(by: disposeBag) - viewModel.selectionResult .bind(to: detailView.selectionResult.rx.text) .disposed(by: disposeBag) @@ -44,16 +42,9 @@ class DetailViewController: UIViewController, ViewController { viewModel.contentsListTitle .bind(to: detailView.contentsListTitle.rx.text) .disposed(by: disposeBag) - - viewModel.content.value - .map { $0.name } - .reduce(BehaviorRelay(value: "")) { - let combined = ($0.value == "") ? $1.value : $0.value + ", " + $1.value - return BehaviorRelay(value: combined) - } + viewModel.contentsListText .bind(to: detailView.contentsList.rx.text) .disposed(by: disposeBag) - viewModel.contentsButtonTitle .bind(to: detailView.contentsButton.rx.title()) .disposed(by: disposeBag) diff --git a/Application/Source/Detail/View/DetailViewModel.swift b/Application/Source/Detail/View/DetailViewModel.swift index a2629b2..c832dc6 100644 --- a/Application/Source/Detail/View/DetailViewModel.swift +++ b/Application/Source/Detail/View/DetailViewModel.swift @@ -27,6 +27,11 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { let content: Property<[Food]> = Property([.beans, .greens, .potatoes, .tomatoes]) + private(set) lazy var contentsListText: Property = { + let listText = createListText(from: content.value) + return Property(listText) + }() + private(set) lazy var presentSelection = makePresentSelection( withFactory: selectionFactory, defaultValue: { [weak self] in @@ -49,6 +54,12 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { private let selectionFactory: SelectionViewModelFactoryProtocol private let disposeBag = DisposeBag() + func createListText(from contents: [ContentType], separatedBy separator: String = ", ") -> String { + let initial = "" + return contents + .map { $0.name.value } + .reduce(initial) { return $0 == initial ? $1 : $0 + separator + $1 } + } } protocol DetailViewModelFactoryProtocol: SelectionViewModelFactoryProtocol { diff --git a/Application/Tests/DetailViewModelSpec.swift b/Application/Tests/DetailViewModelSpec.swift index ec6fcae..c365e72 100644 --- a/Application/Tests/DetailViewModelSpec.swift +++ b/Application/Tests/DetailViewModelSpec.swift @@ -78,8 +78,19 @@ class DetailViewModelSpec: QuickSpec { expect(viewModel.title.value).to(equal(L10n.Detail.title)) } } - } + describe("createListText()") { + it("should create a string list from an array of Contents") { + let testContent: [Food] = [.tomatoes, .potatoes] + let separator = " 🍅🥔 " + let expectedString = L10n.Food.tomatoes + separator + L10n.Food.potatoes + + let actualString = viewModel.createListText(from: testContent, separatedBy: separator) + + expect(actualString).to(equal(expectedString)) + } + } + } } } From 955be856cf30dc41ea42b6c95594a6259b1ac935 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Thu, 17 Jan 2019 21:31:02 -0800 Subject: [PATCH 07/10] Refactor contents list to be more reactive --- .../Source/Detail/View/DetailViewModel.swift | 20 ++++++++++--------- Application/Source/Models/Content.swift | 2 ++ .../SelectionViewControllerStyle.swift | 1 + Application/Tests/DetailViewModelSpec.swift | 13 +++++------- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Application/Source/Detail/View/DetailViewModel.swift b/Application/Source/Detail/View/DetailViewModel.swift index c832dc6..f71d3e8 100644 --- a/Application/Source/Detail/View/DetailViewModel.swift +++ b/Application/Source/Detail/View/DetailViewModel.swift @@ -25,11 +25,19 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { return .empty() } - let content: Property<[Food]> = Property([.beans, .greens, .potatoes, .tomatoes]) + let content: BehaviorRelay<[Food]> = BehaviorRelay(value: [.beans, .greens, .potatoes, .tomatoes]) + + let contentListSeparator: String = ", " private(set) lazy var contentsListText: Property = { - let listText = createListText(from: content.value) - return Property(listText) + let initial = "" + let observable = content.map { [weak self] contents -> String in + guard let self = self else { return initial } + return contents + .map { $0.name.value } + .joined(separator: self.contentListSeparator) + } + return Property(observable, initial: initial) }() private(set) lazy var presentSelection = makePresentSelection( @@ -54,12 +62,6 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { private let selectionFactory: SelectionViewModelFactoryProtocol private let disposeBag = DisposeBag() - func createListText(from contents: [ContentType], separatedBy separator: String = ", ") -> String { - let initial = "" - return contents - .map { $0.name.value } - .reduce(initial) { return $0 == initial ? $1 : $0 + separator + $1 } - } } protocol DetailViewModelFactoryProtocol: SelectionViewModelFactoryProtocol { diff --git a/Application/Source/Models/Content.swift b/Application/Source/Models/Content.swift index 44ba1cc..98e812f 100644 --- a/Application/Source/Models/Content.swift +++ b/Application/Source/Models/Content.swift @@ -14,6 +14,7 @@ protocol Content { - [Crispy.](https://www.youtube.com/watch?v=1BC1G33-fNY) */ enum Food: Content { + case beans, greens, potatoes, tomatoes enum Category: Int { @@ -39,4 +40,5 @@ enum Food: Content { case .tomatoes: return .fruit } } + } diff --git a/Application/Source/Selection/SelectionViewControllerStyle.swift b/Application/Source/Selection/SelectionViewControllerStyle.swift index 2b894e0..9956c4b 100644 --- a/Application/Source/Selection/SelectionViewControllerStyle.swift +++ b/Application/Source/Selection/SelectionViewControllerStyle.swift @@ -3,6 +3,7 @@ import UIKit import Core struct SelectionViewControllerStyle: Style { + let theme: Theme let background: BackgroundViewStyle let textField: TextFieldStyle diff --git a/Application/Tests/DetailViewModelSpec.swift b/Application/Tests/DetailViewModelSpec.swift index c365e72..03d39ef 100644 --- a/Application/Tests/DetailViewModelSpec.swift +++ b/Application/Tests/DetailViewModelSpec.swift @@ -79,15 +79,12 @@ class DetailViewModelSpec: QuickSpec { } } - describe("createListText()") { - it("should create a string list from an array of Contents") { - let testContent: [Food] = [.tomatoes, .potatoes] - let separator = " 🍅🥔 " - let expectedString = L10n.Food.tomatoes + separator + L10n.Food.potatoes + describe("contentsListText") { + it("should return a String list from the array of Contents") { + viewModel.content.accept([.tomatoes, .potatoes]) + let expected = L10n.Food.tomatoes + viewModel.contentListSeparator + L10n.Food.potatoes - let actualString = viewModel.createListText(from: testContent, separatedBy: separator) - - expect(actualString).to(equal(expectedString)) + expect(viewModel.contentsListText.value).to(equal(expected)) } } } From 2d7a2944071c63f3b427f75137a89df6b0521b8c Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 23 Jan 2019 18:04:13 -0800 Subject: [PATCH 08/10] Changes per PR review --- .../Source/Detail/View/DetailView.swift | 12 ++--- .../Detail/View/DetailViewController.swift | 14 +++--- .../View/DetailViewControllerStyle.swift | 18 ++++---- .../Source/Detail/View/DetailViewModel.swift | 22 ++++------ Application/Source/Models/Content.swift | 44 ------------------- Application/Source/Models/Food.swift | 22 ++++++++++ Application/Source/SwiftGen/Strings.swift | 12 ++--- .../Base.lproj/Localizable.strings | 16 ++++--- Application/Tests/DetailViewModelSpec.swift | 8 ++-- Core/Source/Themes/Styles/LabelStyle.swift | 1 + 10 files changed, 75 insertions(+), 94 deletions(-) delete mode 100644 Application/Source/Models/Content.swift create mode 100644 Application/Source/Models/Food.swift diff --git a/Application/Source/Detail/View/DetailView.swift b/Application/Source/Detail/View/DetailView.swift index 9d9879f..b485544 100644 --- a/Application/Source/Detail/View/DetailView.swift +++ b/Application/Source/Detail/View/DetailView.swift @@ -13,9 +13,9 @@ class DetailView: UIView { return view }() - let contentsListTitle = UILabel.centeredLabel() - let contentsList = UILabel.centeredLabel() - let contentsButton = UIButton() + let foodListTitle = UILabel.centeredLabel() + let foodList = UILabel.centeredLabel() + let foodInfoButton = UIButton() private(set) lazy var stackView: UIStackView = { let stackView = UIStackView(arrangedSubviews: [ @@ -23,9 +23,9 @@ class DetailView: UIView { button, selectionResult, spacingView, - contentsListTitle, - contentsList, - contentsButton + foodListTitle, + foodList, + foodInfoButton ]) stackView.axis = .vertical stackView.alignment = .center diff --git a/Application/Source/Detail/View/DetailViewController.swift b/Application/Source/Detail/View/DetailViewController.swift index 23b3b24..cb114a5 100644 --- a/Application/Source/Detail/View/DetailViewController.swift +++ b/Application/Source/Detail/View/DetailViewController.swift @@ -39,18 +39,18 @@ class DetailViewController: UIViewController, ViewController { .bind(to: detailView.selectionResult.rx.text) .disposed(by: disposeBag) - viewModel.contentsListTitle - .bind(to: detailView.contentsListTitle.rx.text) + viewModel.foodListTitle + .bind(to: detailView.foodListTitle.rx.text) .disposed(by: disposeBag) - viewModel.contentsListText - .bind(to: detailView.contentsList.rx.text) + viewModel.foodListText + .bind(to: detailView.foodList.rx.text) .disposed(by: disposeBag) - viewModel.contentsButtonTitle - .bind(to: detailView.contentsButton.rx.title()) + viewModel.foodInfoButtonTitle + .bind(to: detailView.foodInfoButton.rx.title()) .disposed(by: disposeBag) detailView.button.rx.bind(to: viewModel.presentSelection, input: true) - detailView.contentsButton.rx.bind(to: viewModel.presentContents, input: ()) + detailView.foodInfoButton.rx.bind(to: viewModel.presentContents, input: ()) rx.isAppeared .bind(to: viewModel.isActive) diff --git a/Application/Source/Detail/View/DetailViewControllerStyle.swift b/Application/Source/Detail/View/DetailViewControllerStyle.swift index aa145d3..43bc850 100644 --- a/Application/Source/Detail/View/DetailViewControllerStyle.swift +++ b/Application/Source/Detail/View/DetailViewControllerStyle.swift @@ -5,15 +5,15 @@ import Core struct DetailViewControllerStyle: Style { let theme: Theme let background: BackgroundViewStyle - let title: LabelStyle - let alternateCopy: AlternateLabelStyle + let label: LabelStyle + let alternateLabel: AlternateLabelStyle let button: ButtonStyle init(theme: Theme) { self.theme = theme background = BackgroundViewStyle(theme: theme) - title = LabelStyle(theme: theme) - alternateCopy = AlternateLabelStyle(theme: theme) + label = LabelStyle(theme: theme) + alternateLabel = AlternateLabelStyle(theme: theme) button = ButtonStyle(theme: theme) } @@ -21,14 +21,14 @@ struct DetailViewControllerStyle: Style { let view = styleable.detailView background.apply(to: view) - title.apply(to: view.title) - title.apply(to: view.contentsListTitle) + label.apply(to: view.title) + label.apply(to: view.foodListTitle) - alternateCopy.apply(to: view.selectionResult) - alternateCopy.apply(to: view.contentsList) + alternateLabel.apply(to: view.selectionResult) + alternateLabel.apply(to: view.foodList) button.apply(to: view.button) - button.apply(to: view.contentsButton) + button.apply(to: view.foodInfoButton) view.stackView.spacing = theme.layout.interitemSpacing } diff --git a/Application/Source/Detail/View/DetailViewModel.swift b/Application/Source/Detail/View/DetailViewModel.swift index f71d3e8..a70332a 100644 --- a/Application/Source/Detail/View/DetailViewModel.swift +++ b/Application/Source/Detail/View/DetailViewModel.swift @@ -17,27 +17,23 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { let presentSelectionTitle = Property(L10n.Detail.Select.title) - let contentsListTitle = Property(L10n.Detail.ContentsList.title) - let contentsButtonTitle = Property(L10n.Detail.ContentsButton.title) + let foodListTitle = Property(L10n.Detail.FoodList.title) + let foodInfoButtonTitle = Property(L10n.Detail.FoodButton.title) let presentContents = CocoaAction { _ in print("Content button pressed") return .empty() } - let content: BehaviorRelay<[Food]> = BehaviorRelay(value: [.beans, .greens, .potatoes, .tomatoes]) + let foods: BehaviorRelay<[Food]> = BehaviorRelay(value: [.beans, .greens, .potatoes, .tomatoes]) - let contentListSeparator: String = ", " - - private(set) lazy var contentsListText: Property = { - let initial = "" - let observable = content.map { [weak self] contents -> String in - guard let self = self else { return initial } - return contents - .map { $0.name.value } - .joined(separator: self.contentListSeparator) + private(set) lazy var foodListText: Property = { + let observable = foods.map { foods -> String in + return foods + .map { $0.name } + .joined(separator: ", ") } - return Property(observable, initial: initial) + return Property(observable, initial: "") }() private(set) lazy var presentSelection = makePresentSelection( diff --git a/Application/Source/Models/Content.swift b/Application/Source/Models/Content.swift deleted file mode 100644 index 98e812f..0000000 --- a/Application/Source/Models/Content.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation -import RxCocoa - -protocol Content { - associatedtype CategoryType - - var name: BehaviorRelay { get } - var category: CategoryType { get } -} - -/** - It's not a side project if you can't have some fun, right? - - [Original.](https://www.youtube.com/watch?v=amONEHAhLHY) - - [Crispy.](https://www.youtube.com/watch?v=1BC1G33-fNY) - */ -enum Food: Content { - - case beans, greens, potatoes, tomatoes - - enum Category: Int { - case fruit, tuber, vegetable, legume - } - - var name: BehaviorRelay { - let value: String - switch self { - case .beans: value = L10n.Food.beans - case .greens: value = L10n.Food.greens - case .potatoes: value = L10n.Food.potatoes - case .tomatoes: value = L10n.Food.tomatoes - } - return BehaviorRelay(value: value) - } - - var category: Category { - switch self { - case .beans: return .legume - case .greens: return .vegetable - case .potatoes: return .tuber - case .tomatoes: return .fruit - } - } - -} diff --git a/Application/Source/Models/Food.swift b/Application/Source/Models/Food.swift new file mode 100644 index 0000000..546cf6b --- /dev/null +++ b/Application/Source/Models/Food.swift @@ -0,0 +1,22 @@ +import Foundation +import RxCocoa + +/** + It's not a side project if you can't have some fun, right? + - [Original.](https://www.youtube.com/watch?v=amONEHAhLHY) + - [Crispy.](https://www.youtube.com/watch?v=1BC1G33-fNY) + */ +enum Food { + + case beans, greens, potatoes, tomatoes + + var name: String { + switch self { + case .beans: return L10n.Food.beans + case .greens: return L10n.Food.greens + case .potatoes: return L10n.Food.potatoes + case .tomatoes: return L10n.Food.tomatoes + } + } + +} diff --git a/Application/Source/SwiftGen/Strings.swift b/Application/Source/SwiftGen/Strings.swift index 417cdd5..b54610f 100644 --- a/Application/Source/SwiftGen/Strings.swift +++ b/Application/Source/SwiftGen/Strings.swift @@ -15,13 +15,13 @@ internal enum L10n { internal enum Detail { /// Details internal static let title = L10n.tr("Localizable", "detail.title") - internal enum ContentsButton { - /// Contents Info - internal static let title = L10n.tr("Localizable", "detail.contents_button.title") + internal enum FoodButton { + /// Food Info + internal static let title = L10n.tr("Localizable", "detail.food_button.title") } - internal enum ContentsList { - /// Contents - internal static let title = L10n.tr("Localizable", "detail.contents_list.title") + internal enum FoodList { + /// Ingredients + internal static let title = L10n.tr("Localizable", "detail.food_list.title") } internal enum Select { /// Select Text diff --git a/Application/Supporting Files/Base.lproj/Localizable.strings b/Application/Supporting Files/Base.lproj/Localizable.strings index 9d8c06e..a6b8dbd 100644 --- a/Application/Supporting Files/Base.lproj/Localizable.strings +++ b/Application/Supporting Files/Base.lproj/Localizable.strings @@ -7,11 +7,11 @@ /* The title of the tab bar item for the detail navigation flow. */ "detail_navigation.tab_bar_item.title" = "Detail"; -/* Title for the contents list on the details view. */ -"detail.contents_list.title" = "Contents"; +/* Title for the food list on the details view. */ +"detail.food_list.title" = "Ingredients"; -/* Title for the contents button that presents the contents view. */ -"detail.contents_button.title" = "Contents Info"; +/* Title for the food button that presents the contents view. */ +"detail.food_button.title" = "Food Info"; /* The title of the button that presents the detail view. */ "home.present_detail.title" = "Details"; @@ -31,8 +31,14 @@ /* The title of the tab bar item for the settings navigation flow. */ "settings_navigation.tab_bar_item.title" = "Settings"; -/* Food enum names. */ +/* Name of Food: beans. */ "food.beans" = "Beans"; + +/* Name of Food: greens. */ "food.greens" = "Greens"; + +/* Name of Food: potatoes. */ "food.potatoes" = "Potatoes"; + +/* Name of Food: tomatoes. */ "food.tomatoes" = "Tomatoes"; diff --git a/Application/Tests/DetailViewModelSpec.swift b/Application/Tests/DetailViewModelSpec.swift index 03d39ef..3ea3edb 100644 --- a/Application/Tests/DetailViewModelSpec.swift +++ b/Application/Tests/DetailViewModelSpec.swift @@ -79,12 +79,12 @@ class DetailViewModelSpec: QuickSpec { } } - describe("contentsListText") { + describe("foodListText") { it("should return a String list from the array of Contents") { - viewModel.content.accept([.tomatoes, .potatoes]) - let expected = L10n.Food.tomatoes + viewModel.contentListSeparator + L10n.Food.potatoes + viewModel.foods.accept([.tomatoes, .potatoes]) + let expected = L10n.Food.tomatoes + ", " + L10n.Food.potatoes - expect(viewModel.contentsListText.value).to(equal(expected)) + expect(viewModel.foodListText.value).to(equal(expected)) } } } diff --git a/Core/Source/Themes/Styles/LabelStyle.swift b/Core/Source/Themes/Styles/LabelStyle.swift index 85830ea..f52a70c 100644 --- a/Core/Source/Themes/Styles/LabelStyle.swift +++ b/Core/Source/Themes/Styles/LabelStyle.swift @@ -18,4 +18,5 @@ public struct AlternateLabelStyle: UILabelStyle { public init(theme: Theme) { textColor = theme.color.alternateBodyText } + } From dddaa5253fe665aa784486f2cf5b9a422847e662 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 23 Jan 2019 18:37:14 -0800 Subject: [PATCH 09/10] Remove centeredLabel extension in favor of UILabelStyle additions --- Application/Source/Detail/View/DetailView.swift | 8 ++++---- Application/Source/Extensions/Extensions.swift | 11 ----------- Application/Source/Home/View/HomeView.swift | 2 +- Core/Source/Themes/Styles/LabelStyle.swift | 4 ++++ Themer/Source/UIKit/UILabelStyle.swift | 6 ++++++ Themer/Tests/Stubs/StubUILabelStyle.swift | 2 ++ Themer/Tests/UILabelStyleSpec.swift | 10 +++++++++- 7 files changed, 26 insertions(+), 17 deletions(-) delete mode 100644 Application/Source/Extensions/Extensions.swift diff --git a/Application/Source/Detail/View/DetailView.swift b/Application/Source/Detail/View/DetailView.swift index b485544..ae93f66 100644 --- a/Application/Source/Detail/View/DetailView.swift +++ b/Application/Source/Detail/View/DetailView.swift @@ -3,9 +3,9 @@ import SnapKit class DetailView: UIView { - let title = UILabel.centeredLabel() + let title = UILabel() let button = UIButton() - let selectionResult = UILabel.centeredLabel() + let selectionResult = UILabel() private let spacingView: UIView = { let view = UIView() @@ -13,8 +13,8 @@ class DetailView: UIView { return view }() - let foodListTitle = UILabel.centeredLabel() - let foodList = UILabel.centeredLabel() + let foodListTitle = UILabel() + let foodList = UILabel() let foodInfoButton = UIButton() private(set) lazy var stackView: UIStackView = { diff --git a/Application/Source/Extensions/Extensions.swift b/Application/Source/Extensions/Extensions.swift deleted file mode 100644 index c84dc62..0000000 --- a/Application/Source/Extensions/Extensions.swift +++ /dev/null @@ -1,11 +0,0 @@ -import UIKit - -extension UILabel { - - static func centeredLabel() -> UILabel { - let label = UILabel() - label.textAlignment = .center - label.numberOfLines = 0 - return label - } -} diff --git a/Application/Source/Home/View/HomeView.swift b/Application/Source/Home/View/HomeView.swift index 4055ae7..7167175 100644 --- a/Application/Source/Home/View/HomeView.swift +++ b/Application/Source/Home/View/HomeView.swift @@ -3,7 +3,7 @@ import SnapKit class HomeView: UIView { - let label = UILabel.centeredLabel() + let label = UILabel() let imageView: UIImageView = { let imageView = UIImageView() diff --git a/Core/Source/Themes/Styles/LabelStyle.swift b/Core/Source/Themes/Styles/LabelStyle.swift index f52a70c..5322be6 100644 --- a/Core/Source/Themes/Styles/LabelStyle.swift +++ b/Core/Source/Themes/Styles/LabelStyle.swift @@ -4,6 +4,8 @@ import UIKit public struct LabelStyle: UILabelStyle { public let textColor: UIColor + public let numberOfLines: Int = 0 + public let textAlignment: NSTextAlignment = .center public init(theme: Theme) { textColor = theme.color.bodyText @@ -14,6 +16,8 @@ public struct LabelStyle: UILabelStyle { public struct AlternateLabelStyle: UILabelStyle { public let textColor: UIColor + public let numberOfLines: Int = 0 + public let textAlignment: NSTextAlignment = .center public init(theme: Theme) { textColor = theme.color.alternateBodyText diff --git a/Themer/Source/UIKit/UILabelStyle.swift b/Themer/Source/UIKit/UILabelStyle.swift index 49e17e1..218fa22 100644 --- a/Themer/Source/UIKit/UILabelStyle.swift +++ b/Themer/Source/UIKit/UILabelStyle.swift @@ -2,10 +2,16 @@ import UIKit public protocol UILabelStyle: Style where Styleable: UILabel { var textColor: UIColor { get } + var textAlignment: NSTextAlignment { get } + var numberOfLines: Int { get } } public extension UILabelStyle { + public func apply(to styleable: UILabel) { styleable.textColor = textColor + styleable.textAlignment = textAlignment + styleable.numberOfLines = numberOfLines } + } diff --git a/Themer/Tests/Stubs/StubUILabelStyle.swift b/Themer/Tests/Stubs/StubUILabelStyle.swift index 22b6dd5..d002a68 100644 --- a/Themer/Tests/Stubs/StubUILabelStyle.swift +++ b/Themer/Tests/Stubs/StubUILabelStyle.swift @@ -4,5 +4,7 @@ import UIKit struct StubUILabelStyle: UILabelStyle { let textColor: UIColor + let textAlignment: NSTextAlignment + let numberOfLines: Int } diff --git a/Themer/Tests/UILabelStyleSpec.swift b/Themer/Tests/UILabelStyleSpec.swift index 35b2f04..92caa0d 100644 --- a/Themer/Tests/UILabelStyleSpec.swift +++ b/Themer/Tests/UILabelStyleSpec.swift @@ -10,15 +10,23 @@ class UILabelStyleSpec: QuickSpec { describe("UILabelStyle") { it("should update appropriate values") { let textColor: UIColor = .red + let textAlignment: NSTextAlignment = .right + let numberOfLines = 99 let style = StubUILabelStyle( - textColor: textColor) + textColor: textColor, + textAlignment: textAlignment, + numberOfLines: numberOfLines) let label = UILabel() expect(label.textColor).notTo(equal(textColor)) + expect(label.textAlignment).notTo(equal(textAlignment)) + expect(label.numberOfLines).notTo(equal(numberOfLines)) style.apply(to: label) expect(label.textColor).to(equal(textColor)) + expect(label.textAlignment).to(equal(textAlignment)) + expect(label.numberOfLines).to(equal(numberOfLines)) } } From 631fe8af4228ef3b8e2f1244bdc10334b795e8e7 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Sat, 26 Jan 2019 17:31:45 -0800 Subject: [PATCH 10/10] Use container stackView instead of spacingView --- .../Source/Detail/View/DetailView.swift | 33 ++++++++++++------- .../View/DetailViewControllerStyle.swift | 6 +++- Core/Source/Themes/Themes.swift | 12 +++++++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Application/Source/Detail/View/DetailView.swift b/Application/Source/Detail/View/DetailView.swift index ae93f66..6491de1 100644 --- a/Application/Source/Detail/View/DetailView.swift +++ b/Application/Source/Detail/View/DetailView.swift @@ -7,22 +7,23 @@ class DetailView: UIView { let button = UIButton() let selectionResult = UILabel() - private let spacingView: UIView = { - let view = UIView() - view.snp.makeConstraints { $0.height.equalTo(35) } - return view - }() - let foodListTitle = UILabel() let foodList = UILabel() let foodInfoButton = UIButton() - private(set) lazy var stackView: UIStackView = { + private(set) lazy var selectionStackView: UIStackView = { let stackView = UIStackView(arrangedSubviews: [ title, button, - selectionResult, - spacingView, + selectionResult + ]) + stackView.axis = .vertical + stackView.alignment = .center + return stackView + }() + + private(set) lazy var foodStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [ foodListTitle, foodList, foodInfoButton @@ -32,13 +33,23 @@ class DetailView: UIView { return stackView }() + private(set) lazy var containerStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [ + selectionStackView, + foodStackView + ]) + stackView.axis = .vertical + stackView.alignment = .center + return stackView + }() + let requiresConstraintBasedLayout = true override init(frame: CGRect) { super.init(frame: frame) - addSubview(stackView) - stackView.snp.makeConstraints { make in + addSubview(containerStackView) + containerStackView.snp.makeConstraints { make in make.center.equalTo(self) } } diff --git a/Application/Source/Detail/View/DetailViewControllerStyle.swift b/Application/Source/Detail/View/DetailViewControllerStyle.swift index 43bc850..40ee318 100644 --- a/Application/Source/Detail/View/DetailViewControllerStyle.swift +++ b/Application/Source/Detail/View/DetailViewControllerStyle.swift @@ -3,6 +3,7 @@ import UIKit import Core struct DetailViewControllerStyle: Style { + let theme: Theme let background: BackgroundViewStyle let label: LabelStyle @@ -30,7 +31,10 @@ struct DetailViewControllerStyle: Style { button.apply(to: view.button) button.apply(to: view.foodInfoButton) - view.stackView.spacing = theme.layout.interitemSpacing + view.selectionStackView.spacing = theme.layout.interitemSpacing + view.foodStackView.spacing = theme.layout.interitemSpacing + + view.containerStackView.spacing = theme.layout.containerSpacing } } diff --git a/Core/Source/Themes/Themes.swift b/Core/Source/Themes/Themes.swift index 19544ee..ceb1f88 100644 --- a/Core/Source/Themes/Themes.swift +++ b/Core/Source/Themes/Themes.swift @@ -6,6 +6,7 @@ public enum Theme { } public extension Theme { + public var color: ColorSet { switch self { case .light: return ColorSet( @@ -37,14 +38,17 @@ public extension Theme { public var layout: Layout { return Layout( interitemSpacing: 20, + containerSpacing: 35, buttonContentEdgeInsets: UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20), buttonBorderWidth: 1, buttonCornerRadius: 12, contentLayoutMargins: UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)) } + } public struct ColorSet { + public let bodyText: UIColor public let alternateBodyText: UIColor public let inputText: UIColor @@ -52,12 +56,20 @@ public struct ColorSet { public let viewBackground: UIColor? public let navigationBarTint: UIColor? public let tabBarTint: UIColor? + } public struct Layout { + + /// Spacing between elements within an inner (or single) stackView. public let interitemSpacing: CGFloat + + /// Spacing between inner stackViews within an outer container stackView. + public let containerSpacing: CGFloat + public let buttonContentEdgeInsets: UIEdgeInsets public let buttonBorderWidth: CGFloat public let buttonCornerRadius: CGFloat public let contentLayoutMargins: UIEdgeInsets + }