diff --git a/Bento/Bento/Node.swift b/Bento/Bento/Node.swift index fd97518..87903fe 100644 --- a/Bento/Bento/Node.swift +++ b/Bento/Bento/Node.swift @@ -1,5 +1,8 @@ import UIKit +/// Node is kept by a Section. Node requires an identifier for diff algorithm. +/// Node **always has** a visual representation, because a component needs to be provided. +/// To simplify, you can think of a Node in Bento as equivalent to a cell in UITableView / UICollectionView. public struct Node { public let id: Identifier let component: AnyRenderable diff --git a/Bento/Bento/Section.swift b/Bento/Bento/Section.swift index 14c4a54..ad4763e 100644 --- a/Bento/Bento/Section.swift +++ b/Bento/Bento/Section.swift @@ -1,5 +1,8 @@ import UIKit +/// Section is part of a Box and holds an array of nodes. SectionID needs to be provided for the diffing algorithm. +/// Section **can** have a visual representation if components are provided for the header and/or footer. +/// To simplify, you can think of a Section in Bento as equivalent to a section in a UITableView. public struct Section { public typealias Item = Node diff --git a/Bento/Renderable/Renderable.swift b/Bento/Renderable/Renderable.swift index ad5979e..17cad35 100644 --- a/Bento/Renderable/Renderable.swift +++ b/Bento/Renderable/Renderable.swift @@ -1,5 +1,7 @@ import UIKit +/// Protocol which every Component needs to conform to. +/// - View: UIView subtype which is the top level view type of the component. public protocol Renderable { associatedtype View: NativeView diff --git a/Bento/Views/Box.swift b/Bento/Views/Box.swift index d4f57c4..c19c31c 100644 --- a/Bento/Views/Box.swift +++ b/Bento/Views/Box.swift @@ -16,6 +16,8 @@ infix operator |---+: NodeConcatenationPrecedence infix operator |---*: NodeConcatenationPrecedence infix operator |---?: NodeConcatenationPrecedence +/// Box holds an array of sections which should be displayed on the screen. +/// Box **doesn't** have a visual representation. public struct Box { public typealias Section = Bento.Section diff --git a/BentoKit/BentoKit/Components/Component.swift b/BentoKit/BentoKit/Components/Component.swift index 2893aea..d8141e7 100644 --- a/BentoKit/BentoKit/Components/Component.swift +++ b/BentoKit/BentoKit/Components/Component.swift @@ -1 +1,2 @@ +/// Namespace for all Components so they are easily located with Xcode hints. public enum Component {} diff --git a/BentoKit/BentoKit/Components/DatePicker.swift b/BentoKit/BentoKit/Components/DatePicker.swift index f7e10ba..ea1d7e8 100644 --- a/BentoKit/BentoKit/Components/DatePicker.swift +++ b/BentoKit/BentoKit/Components/DatePicker.swift @@ -6,6 +6,13 @@ extension Component { public let configurator: (View) -> Void public let styleSheet: StyleSheet + /// Creates Component.DatePicker + /// - parameter date: Date which should be preselected when opening the DatePicker. + /// - parameter minDate: Minimum allowable date. + /// - parameter maxDate: Maximum allowable date. + /// - parameter datePickerMode: Mode of a picker (date, date & time, etc.). + /// - parameter styleSheet: StyleSheet with styling. + /// - parameter didPickDate: Closure which is invoked when a date is selected. public init( date: Date? = nil, minDate: Date? = nil, diff --git a/BentoKit/BentoKit/Components/MultilineTextInput.swift b/BentoKit/BentoKit/Components/MultilineTextInput.swift index 6e4f713..82c0e23 100644 --- a/BentoKit/BentoKit/Components/MultilineTextInput.swift +++ b/BentoKit/BentoKit/Components/MultilineTextInput.swift @@ -11,6 +11,13 @@ extension Component { public let focusEligibility: FocusEligibility public let styleSheet: Component.MultilineTextInput.StyleSheet + /// Creates Component.MultilineTextInput + /// - parameter text: Value of the input which should be presented. Can have more than 1 line. + /// - parameter placeholder: Placeholder which is displayed when `text` is empty. + /// - parameter showsFocusToolbar: Shows/hides inputAccessoryView of the TextView. + /// - parameter didChangeText: Closure which notifies of text changes. + /// - parameter didFinishEditing: Closure which notifies when user finishes editing. + /// - parameter styleSheet: StyleSheet with styling. public init( text: String, placeholder: String, diff --git a/BentoKit/BentoKit/Components/OptionPicker.swift b/BentoKit/BentoKit/Components/OptionPicker.swift index f2067f6..868e5c4 100644 --- a/BentoKit/BentoKit/Components/OptionPicker.swift +++ b/BentoKit/BentoKit/Components/OptionPicker.swift @@ -7,6 +7,12 @@ extension Component { public let configurator: (View) -> Void public let styleSheet: StyleSheet + /// Creates Component.OptionPicker + /// - parameter options: Possible options to pick + /// - parameter selected: Option which should be preselected. + /// Has to be an element of `options` + /// - parameter didPickItem: Closure inovked when user picks an option + /// - parameter styleSheet: StyleSheet with styling for the view public init( options: [Option], selected: Option?, diff --git a/BentoKit/BentoKit/Components/Search.swift b/BentoKit/BentoKit/Components/Search.swift index 75b27a4..29038e2 100644 --- a/BentoKit/BentoKit/Components/Search.swift +++ b/BentoKit/BentoKit/Components/Search.swift @@ -5,7 +5,15 @@ extension Component { public final class Search: AutoRenderable { public let configurator: (View) -> Void public let styleSheet: StyleSheet - + + /// Creates Component.Search which representes UISearchBar from iOS. + /// - parameter placeholder: Text displayed when searchField is empty. + /// - parameter keyboardType: Type of the keybaord used for editing. + /// - parameter didBeginEditing: Notifies when searchBar becomes first responder. + /// - parameter textDidChange: Notifies when text changes. + /// - parameter showsCancelButton: Shows/hides the cancel button. + /// - parameter cancelButtonClicked: Notifies that cancel button has been clicked. + /// - parameter styleSheet: StyleSheet with styling. public init( placeholder: String? = nil, keyboardType: UIKeyboardType = .default, diff --git a/BentoKit/BentoKit/Components/TextInput.swift b/BentoKit/BentoKit/Components/TextInput.swift index 5e408c7..f20a8ac 100644 --- a/BentoKit/BentoKit/Components/TextInput.swift +++ b/BentoKit/BentoKit/Components/TextInput.swift @@ -9,6 +9,17 @@ extension Component { public let styleSheet: StyleSheet public let focusEligibility: FocusEligibility + /// Creates Component.TextInput + /// - parameter title: Label of a the text input which describes it (i.e: "E-mail"). + /// - parameter placeholder: Placeholder which is displayed instead of text when text is empty. + /// - parameter text: Pre-filled value of the TextInput. It can be modifed by user. (i.e: "email@email.com"). Limited to only one line. + /// - parameter keyboardType: UIKit keyboard type. + /// - parameter isEnabled: Indicates if user can change a text value. + /// - parameter accessory: Additional icon displayed on the right side. + /// - parameter textWillChange: Closure which may rejected text changes. + /// - parameter textDidChange: Closure which notifies about text changes. + /// - parameter didTapAccessory: Closure which is invoked when user taps on the accessory. + /// - parameter styleSheet: StyleSheet with styling. public init( title: String? = nil, placeholder: String? = nil, diff --git a/BentoKit/BentoKit/Components/TitledDescription.swift b/BentoKit/BentoKit/Components/TitledDescription.swift index 2b6a6ba..f3f3889 100644 --- a/BentoKit/BentoKit/Components/TitledDescription.swift +++ b/BentoKit/BentoKit/Components/TitledDescription.swift @@ -34,6 +34,18 @@ public extension Component { return heightComputer(width, inheritedMargins) } + /// Creates a TitledDescription + /// - parameter texts: Array of TextValues which are displayed vertically on left side of the row. + /// - parameter detail: Description field displayed to the right of `texts` + /// - parameter image: Usually an image wrapped within `ImageOrLabel` component. Property makes it possible to be loaded async. + /// However, you need to guarantee fixed size across all changes. + /// - parameter accessory: Accessory which should be displayed near the right edge of the row. + /// - parameter badgeIcon: Badge icon which is displayed in the bottom-right corner of the `image`. + /// - parameter inputNodes: Input's component which is displayed when the `TitledDescription` becomes the first responder. + /// - parameter didTap: Closure which is invoked when whole row is tapped. + /// - parameter didTapAccessory: Closure which is invoked when tapping on the accessory. + /// - parameter interactionBehavior: Defines an behaviour when tapped. Usually `.becomeFirstResponder`. + /// - parameter styleSheet: StyleSheet how view should be styled (fonts, colors, text alignment) public init( texts: [TextValue] = [], detail: TextValue? = nil, diff --git a/BentoKit/BentoKit/Components/Toggle.swift b/BentoKit/BentoKit/Components/Toggle.swift index c46f6d7..5bfef8c 100644 --- a/BentoKit/BentoKit/Components/Toggle.swift +++ b/BentoKit/BentoKit/Components/Toggle.swift @@ -8,6 +8,16 @@ extension Component { public let configurator: (View) -> Void public let styleSheet: StyleSheet + /// Creates Component.Toggle + /// - parameter title: Name/label/title of a row which is displayed on the left side. + /// - parameter attributedTitle: The same as `title` but allows for using attributed text. + /// - parameter image: Icon displayed on the left side of the row. + /// - parameter isOn: Indicates if the toggle shows true or false. + /// - parameter isEnabled: Indicates if user can change value of the toggle. + /// - parameter isRefreshing: If `true` it displays `UIActivityIndicator` instead of the switch view. + /// - parameter animateValueChange: Indicates if should animate setting of the `isOn` value. + /// - parameter styleSheet: StyleSheet with styling. + /// - parameter didChangeValue: Closure which notifies about true/false change. public init( title: String, attributedTitle: NSAttributedString? = nil, diff --git a/BentoKit/BentoKit/Helpers/BoxTableViewAdapter.swift b/BentoKit/BentoKit/Helpers/BoxTableViewAdapter.swift index f971b86..7774560 100644 --- a/BentoKit/BentoKit/Helpers/BoxTableViewAdapter.swift +++ b/BentoKit/BentoKit/Helpers/BoxTableViewAdapter.swift @@ -1,5 +1,8 @@ import Bento +/// - Warning: When you use `BoxTableViewAdapter` your `tableView.render(box)` cannot be invoked in `viewWillAppear`. We suggest to call it in +/// `viewDidAppear`. UIKit changes UITableView's `layoutMargins` in between `viewWillAppear` & `viewDidAppear`. The UITableView's `layoutMargins` is used +/// in the BoxTableViewAdapter's implementation. public final class BoxTableViewAdapter : TableViewAdapterBase, UITableViewDataSource, diff --git a/Documentation/common_usecases.md b/Documentation/common_usecases.md new file mode 100644 index 0000000..5eb46fb --- /dev/null +++ b/Documentation/common_usecases.md @@ -0,0 +1,10 @@ +#Common UseCases +## Invoke action while tapping on a cell +The effect is similar to using `tableView(_:didSelectRowAt:)` method on `UITableViewDelegate`. You need to do few steps to be able to react on tapping on a cell. +1. `BentoKit` has a class named `InteractiveView`. Your component's view needs to inherit from this class. +2. Add `didTap: (() -> Void)?` closure to your component's init. +3. Bind `didTap` with the component view's `highlightingGesture` property in the component's `render(in:)` function: +```swift +//didTap's type is (() -> Void)? +view.highlightingGesture.didTap = didTap.map(HighlightingGesture.TapAction.resign) +``` \ No newline at end of file diff --git a/Example/AppDelegate.swift b/Example/AppDelegate.swift index c823851..49cc4db 100644 --- a/Example/AppDelegate.swift +++ b/Example/AppDelegate.swift @@ -4,4 +4,3 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? } - diff --git a/README.md b/README.md index f5eecbe..ac5cb12 100644 --- a/README.md +++ b/README.md @@ -13,29 +13,49 @@ In our experience it makes UI-related code easier to build and maintain. Our aim ## Content ๐Ÿ“‹ +- [Installation](#installation-) - [What's it like?](#whats-it-like-) - [How does it work?](#how-does-it-work-) +- [BentoKit & StyleSheets](#bentokit--stylesheets-) - [Examples](#examples-) -- [Installation](#installation-) +- [Additional documentation](#additional-documentation-) - [Development installation](#development-installation-) - [State of the project](#state-of-the-project-%EF%B8%8F) - [Development Resources](#development-resources) - [Contributing](#contributing-%EF%B8%8F) +### Installation ๐Ÿ’พ + +* Cocoapods + +```ruby +target 'MyApp' do + pod 'Bento' + + # Optionally, if you want to install BentoKit & StyleSheets + pod 'BentoKit' + pod 'StyleSheets' +end +``` + +* Carthage + +``` +github "Babylonpartners/Bento" +``` + ### What's it like? ๐Ÿง When building a `Box`, all you need to care about are `Sections`s and `Node`s. ```swift let box = Box.empty - |-+ Section(id: SectionId.user, - header: EmptySpaceComponent(height: 24, color: .clear)) - |---+ RowId.user <> IconTitleDetailsComponent(icon: image, title: patient.name) - |-+ Section(id: SectionId.consultantDate, - header: EmptySpaceComponent(height: 24, color: .clear)) - |---+ RowId.loading <> LoadingIndicatorComponent(isLoading: true) - -tableView.render(box) + |-+ Section(id: SectionId.user,header: EmptySpaceComponent(height: 24, color: .clear)) + |---+ Node(id: RowID.user, component: IconTitleDetailsComponent(icon: image, title: patient.name)) + |-+ Section(id: SectionId.consultantDate, header: EmptySpaceComponent(height: 24, color: .clear)) + |---+ Node(id: RowID.loading, component: LoadingIndicatorComponent(isLoading: true)) + + tableView.render(box) ``` ### How does it work? ๐Ÿค” @@ -44,7 +64,7 @@ tableView.render(box) Bento automatically performs the data source and delegate setup upon the very first time `UITableView` or `UICollectionView` is asked to render a Bento `Box`. -In other words, for Bento to work, it cannot be overriden with your own data source and delegate. If you wish to respond to delegate messages which Bento does not support as a feature, you may consider supplying a custom adapter using `prepareForBoxRendering(_:)`. +In other words, for Bento to work, it cannot be overridden with your own data source and delegate. If you wish to respond to delegate messages which Bento does not support as a feature, you may consider supplying a custom adapter using `prepareForBoxRendering(_:)`. | Collection View | Adapter Base Class | Required Protocol Conformances | | ---- | ---- | ---- | @@ -53,7 +73,7 @@ In other words, for Bento to work, it cannot be overriden with your own data sou #### Box ๐Ÿ“ฆ -`Box ` is a fundamental component of the library, essentially a virtual representation of the `UITableView` content. It has two generic parameters - `SectionId` and `RowId` - which are unique identifiers for `Section` and `Node`, used by the [diffing engine](https://github.com/RACCommunity/FlexibleDiff) to perform animated changes of the `UITableView` content. +`Box ` is a fundamental component of the library, essentially a virtual representation of the `UITableView` content. It has two generic parameters - `SectionId` and `RowId` - which are unique identifiers for `Section` and `Node`, used by the [diffing engine](https://github.com/RACCommunity/FlexibleDiff) to perform animated changes of the `UITableView` content. Box is just a container for an array of sections. #### Sections and Nodes ๐Ÿ— @@ -134,28 +154,26 @@ precedencegroup SectionConcatenationPrecedence { higherThan: AdditionPrecedence } -infix operator <>: ComposingPrecedence infix operator |-+: SectionConcatenationPrecedence infix operator |-?: SectionConcatenationPrecedence infix operator |---+: NodeConcatenationPrecedence infix operator |---?: NodeConcatenationPrecedence -let bento = Box.empty // 3 - |-+ Section() // 2 - |---+ RowId.id <> Component() // 1 +let bento = Box.empty + |-+ Section(id: SectionID.first) // 2 + |---+ Node(id: RowID.someId, Component()) // 1 ``` As you might have noticed: -* `<>` has `ComposingPrecedence`; +* `|-+` has `SectionConcatenationPrecedence`; * `|---+` has `NodeConcatenationPrecedence` -`<> / NodeConcatenationPrecedence` is higher than `|-+ / SectionConcatenationPrecedence`, meaning Nodes will be computed first. +`NodeConcatenationPrecedence` is higher than `|-+ / SectionConcatenationPrecedence`, meaning Nodes will be computed first. The order of the expression above is: -1. `RowId.id <> Component()` => `Node` -2. `Section() |---+ Node()` => `Section` -3. `Box() |-+ Section()` => `Box` +1. `Section() |---+ Node()` => `Section` +2. `Box() |-+ Section()` => `Box` #### Conditional operators โ“ @@ -174,33 +192,49 @@ let box = Box.empty ``` ```swift let box = Box.empty - ย  ย |-? .some(anOptional) { theOptional in // <-- the value of anOptional unwrapped + ย  ย |-? anOptional.map { unwrappedOptional in // <-- the value of anOptional unwrapped ย  ย  ย  ย Section() // <-- Section only added if `anOptional` is not `nil` ย  ย } ``` -`|---?` works in exactly the same way for `Node`s +`|---?` works in exactly the same way for `Node`. -### Examples ๐Ÿ˜Ž +### BentoKit & StyleSheets ๐ŸŽจ +BentoKit it's a set of generic components like `TiteledDescription`, `Description`, `TextInput`, `EmptySpace` etc. BentoKit uses StyleSheets to style components. -Sections | Appointment | Movies ---- | --- | --- -![](Resources/example1.gif) | ![](Resources/example2.gif) | ![](Resources/example3.gif) +StyleSheets are a way to define **how** particular view should be rendered. Component's job is to provide **what** should be displayed while StyleSheets provide a style **how** it's done. Fonts, colors, alignment should go into StyleSheet. -### Installation ๐Ÿ’พ +StyleSheets support KeyPaths for easier composition. -* Cocoapods - -```ruby -target 'MyApp' do - pod 'Bento' -end +```swift +let styleSheet = LabelStyleSheet() + .compose(\.numberOfLines, 3) + .compose(\.font, UIFont.preferredFont(forTextStyle: .body)) ``` -* Carthage +StyleSheets can be used with BentoKit's components. All you need to do is to use correct stylesheet: + +```swift +return .empty + |-+ Section(id: .first) + |---+ Node( + id: .componentId, + component: Component.Description( + text: "Text", + styleSheet: Component.Description.StyleSheet() + .compose(\.text.font, UIFont.preferredFont(forTextStyle: .body)) + ) + ) ``` -github "Babylonpartners/Bento" -``` + +### Examples ๐Ÿ˜Ž + +Movies | CollectionView | SignUp | +--- | --- | --- +![](Resources/example1.gif) | ![](Resources/example2.gif) | ![](Resources/example3.gif) | + +### Additional documentation ๐Ÿ“™ +- [Common use cases](./Documentation/common_usecases.md) ### Development Installation ๐Ÿ›  @@ -210,12 +244,6 @@ If you want to clone the repo for contributing or for running the example app yo git submodule update --init --recursive ``` -Or, if you have Carthage installed, you can use it to do the same thing: - -``` -carthage checkout -``` - ### State of the project ๐Ÿคทโ€โ™‚๏ธ Feature | Status diff --git a/Resources/example1.gif b/Resources/example1.gif index becec8a..6eeecc6 100644 Binary files a/Resources/example1.gif and b/Resources/example1.gif differ diff --git a/Resources/example2.gif b/Resources/example2.gif index d2651e1..5dd7fcf 100644 Binary files a/Resources/example2.gif and b/Resources/example2.gif differ diff --git a/Resources/example3.gif b/Resources/example3.gif index 764b5a9..45ad03f 100644 Binary files a/Resources/example3.gif and b/Resources/example3.gif differ diff --git a/StyleSheets/StyleSheets/StyleSheet.swift b/StyleSheets/StyleSheets/StyleSheet.swift index 4a8cb7c..e4ca300 100644 --- a/StyleSheets/StyleSheets/StyleSheet.swift +++ b/StyleSheets/StyleSheets/StyleSheet.swift @@ -1,5 +1,6 @@ import UIKit +///Protocol for StyleSheets to allow using `compose()` to modify it's value using KeyPaths public protocol StyleSheetProtocol { associatedtype Element diff --git a/StyleSheets/StyleSheets/ViewStyleSheet.swift b/StyleSheets/StyleSheets/ViewStyleSheet.swift index 2062a41..1b03842 100644 --- a/StyleSheets/StyleSheets/ViewStyleSheet.swift +++ b/StyleSheets/StyleSheets/ViewStyleSheet.swift @@ -1,5 +1,9 @@ import UIKit +/// Basic StyleSheet. +/// StyleSheets can be used to provide how components should look like. +/// Component should only takes data which reflects what is rendered. +/// StyleSheet's job is to provide how component's view should looks like. open class ViewStyleSheet: StyleSheetProtocol { // MARK: Visual Appearance