├── Tests
├── Utils
│ ├── .gitkeep
│ └── Array+UtilsTest.swift
├── StaticMap
│ └── .gitkeep
├── TableView
│ └── .gitkeep
├── Validation
│ └── .gitkeep
├── ActivityIndicator
│ └── .gitkeep
├── CollectionView
│ └── .gitkeep
├── Core
│ ├── Component
│ │ ├── Note.swift
│ │ ├── ComponentTestParts.swift
│ │ └── ComponentBaseTest.swift
│ └── Styling
│ │ ├── UILabel+InitTest.swift
│ │ ├── UIFont+InitTest.swift
│ │ ├── UITableView+InitTest.swift
│ │ ├── CGPoint+InitTest.swift
│ │ ├── AttributeTest.swift
│ │ ├── UICollectionView+InitTest.swift
│ │ ├── UIOffset+InitTest.swift
│ │ ├── CGSize+InitTest.swift
│ │ ├── PercentUtilsTest.swift
│ │ ├── UIColor+InitTest.swift
│ │ ├── NSAttributedString+AttributeTest.swift
│ │ ├── CGAffineTransform+ShortcutTest.swift
│ │ ├── UIButton+UtilsTest.swift
│ │ └── StyleableTest.swift
├── TestUtils
│ ├── ComponentBase+spy.swift
│ └── Rx+recording.swift
├── Info.plist
└── Configuration
│ ├── PropertyTest.swift
│ ├── ConfigurableTest.swift
│ └── ConfigurationTest.swift
├── CNAME
├── _config.yml
├── docs
├── _config.yml
├── parts
│ ├── controller.md
│ ├── collectionview.md
│ ├── staticmap.md
│ ├── validation.md
│ ├── rootview.md
│ ├── wireframe.md
│ └── tableview.md
├── img
│ ├── ReactantTutorial.png
│ ├── ReactantLiveUIDebug.png
│ ├── ReactantLiveUIError.png
│ ├── Tutorials
│ │ ├── RunButton.png
│ │ ├── Notes
│ │ │ ├── Simulator1.png
│ │ │ ├── Simulator2.png
│ │ │ └── Reactant-init-console.png
│ │ └── GitExplorer
│ │ │ ├── Users.png
│ │ │ ├── Repositories.png
│ │ │ └── UserHeader.png
│ └── ReactantLiveUIPreview.png
├── scripts
│ └── zopim.js
├── docpress.json
├── README.md
└── getting-started
│ └── troubleshooting.md
├── TVPrototyping
├── Assets.xcassets
│ ├── Contents.json
│ ├── App Icon & Top Shelf Image.brandassets
│ │ ├── App Icon.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Middle.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── App Icon - App Store.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Middle.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Top Shelf Image.imageset
│ │ │ └── Contents.json
│ │ ├── Top Shelf Image Wide.imageset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ └── LaunchImage.launchimage
│ │ └── Contents.json
├── Components
│ ├── TabController
│ │ └── TabController.swift
│ ├── StaticMap
│ │ └── MapController.swift
│ ├── TableView
│ │ └── TableViewController.swift
│ └── ButtonTest
│ │ └── ButtonController.swift
├── AppDelegate.swift
└── Info.plist
├── ReactantPrototyping
├── Assets.xcassets
│ ├── Contents.json
│ ├── falcon.imageset
│ │ ├── falcon.jpg
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── AppDelegate.swift
├── Info.plist
└── Base.lproj
│ └── LaunchScreen.storyboard
├── Source
├── Validation
│ ├── Rules
│ │ ├── Rules.swift
│ │ ├── Error
│ │ │ └── String+EmailValidationError.swift
│ │ └── Rules+String.swift
│ ├── ValidationError.swift
│ └── Rule.swift
├── TableView
│ ├── TableViewState.swift
│ ├── Properties+TableView.swift
│ ├── ReactantTableView.swift
│ ├── Identifier
│ │ ├── TableViewHeaderFooterIdentifier.swift
│ │ ├── AnyTableViewHeaderFooterIdentifier.swift
│ │ ├── AnyTableViewCellIdentifier.swift
│ │ └── TableViewCellIdentifier.swift
│ ├── Configuration+TableView.swift
│ ├── TableViewCell.swift
│ ├── Internal
│ │ └── TableViewHeaderFooterWrapper.swift
│ └── Implementation
│ │ └── PlainTableView.swift
├── Configuration
│ ├── SubConfiguration.swift
│ ├── Configuration+Style.swift
│ ├── BaseSubConfiguration.swift
│ ├── OptionalType.swift
│ ├── Properties.swift
│ ├── Configurable.swift
│ ├── Property.swift
│ └── Configuration.swift
├── Utils
│ ├── Recycler
│ │ ├── UIView+Reusable.swift
│ │ ├── Reusable.swift
│ │ └── SynchronizedRecycler.swift
│ ├── UIImage+Utils.swift
│ ├── Scrollable
│ │ ├── Scrollable.swift
│ │ └── UIScrollView+Scrollable.swift
│ ├── Swift+Compatibility.swift
│ ├── Dictionary+Utils.swift
│ ├── Array+Utils.swift
│ ├── Visibility
│ │ ├── Visibility.swift
│ │ ├── ConstraintAction.swift
│ │ └── CollapseAxis.swift
│ ├── RxUtils
│ │ ├── ObserverType+Utils.swift
│ │ └── UIBarButtonItem+ClosureAction.swift
│ ├── UINavigationController+DialogDismissalListener.swift
│ ├── AssociatedObject.swift
│ ├── Hash.swift
│ ├── UIStackView+ArrangedChildren.swift
│ ├── Collection+Utils.swift
│ ├── RangeReplaceableCollection+Utils.swift
│ ├── Internal
│ │ └── InternalUtils.swift
│ └── UIView+Utils.swift
├── Core
│ ├── Styling
│ │ ├── PercentUtils.swift
│ │ ├── Extensions
│ │ │ ├── UILabel+Init.swift
│ │ │ ├── UITableView+Init.swift
│ │ │ ├── UIFont+Init.swift
│ │ │ ├── UICollectionView+Init.swift
│ │ │ ├── CGPoint+Init.swift
│ │ │ ├── CGSize+Init.swift
│ │ │ ├── UIOffset+Init.swift
│ │ │ ├── CGAffineTransform+Shortcut.swift
│ │ │ ├── UIButton+Utils.swift
│ │ │ ├── UITabBarController+Styles.swift
│ │ │ ├── UINavigationController+Styles.swift
│ │ │ ├── UIEdgeInsets+Init.swift
│ │ │ ├── CGRect+Init.swift
│ │ │ └── UIColor+Init.swift
│ │ └── AttributedString
│ │ │ └── NSAttributedString+Attribute.swift
│ ├── Properties+Style.swift
│ ├── Controller
│ │ ├── DialogDismissalListener.swift
│ │ ├── DialogControllerBase.swift
│ │ └── ScrollControllerBase.swift
│ ├── View
│ │ ├── RootView.swift
│ │ ├── Internal
│ │ │ ├── DialogView.swift
│ │ │ └── ControllerRootViewContainer.swift
│ │ ├── ContainerView.swift
│ │ └── Impl
│ │ │ └── PickerView.swift
│ ├── Component
│ │ ├── ReactantUI.swift
│ │ └── ComponentBase.swift
│ ├── Wireframe
│ │ ├── Internal
│ │ │ └── FutureControllerProvider.swift
│ │ └── UIViewController+Navigation.swift
│ └── Properties+Core.swift
├── CollectionView
│ ├── CollectionViewCell.swift
│ ├── FlowCollectionViewBase.swift
│ ├── Properties+CollectionView.swift
│ ├── ReactantCollectionView.swift
│ ├── Identifier
│ │ ├── AnyCollectionViewCellIdentifier.swift
│ │ ├── AnyCollectionSupplementaryViewIdentifier.swift
│ │ ├── CollectionSupplementaryViewIdentifier.swift
│ │ └── CollectionViewCellIdentifier.swift
│ ├── ReactantCollectionView+Delegates.swift
│ ├── Configuration+CollectionView.swift
│ ├── Internal
│ │ ├── CollectionReusableViewWrapper.swift
│ │ └── CollectionViewCellWrapper.swift
│ ├── Implementation
│ │ ├── SimpleCollectionView.swift
│ │ └── PagingCollectionView.swift
│ └── CollectionViewState.swift
├── StaticMap
│ ├── MKCoordinateRegion+utils.swift
│ ├── MKCoordinateSpan+inset.swift
│ └── CLLocationCoordinate2D+utils.swift
└── Info.plist
├── Example
├── Reactant.xcodeproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
├── Reactant.xcworkspace
│ └── contents.xcworkspacedata
├── Application
│ ├── Sources
│ │ ├── DependencyModule.swift
│ │ ├── AppModule.swift
│ │ ├── Service
│ │ │ └── NameService.swift
│ │ ├── Components
│ │ │ ├── Table
│ │ │ │ ├── TableRootView.swift
│ │ │ │ └── TableViewController.swift
│ │ │ └── Main
│ │ │ │ ├── LabelView.swift
│ │ │ │ ├── MainViewController.swift
│ │ │ │ └── MainRootView.swift
│ │ ├── AppDelegate.swift
│ │ └── Wireframe
│ │ │ └── MainWireframe.swift
│ └── Resources
│ │ ├── Images.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Info.plist
├── Podfile
├── Tests
│ ├── Supporting Files
│ │ └── Info.plist
│ └── Tests.swift
└── Podfile.lock
├── Reactant.xcodeproj
└── project.xcworkspace
│ └── contents.xcworkspacedata
├── Reactant.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── .gitignore
├── Podfile
├── LICENSE
├── .travis.yml
├── CHANGELOG.md
└── README.md
/Tests/Utils/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | docs.reactant.tech
2 |
--------------------------------------------------------------------------------
/Tests/StaticMap/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/TableView/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/Validation/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/ActivityIndicator/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/CollectionView/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/docs/parts/controller.md:
--------------------------------------------------------------------------------
1 | # Controllers
2 |
3 | **Documentation will be available shortly.**
4 |
--------------------------------------------------------------------------------
/docs/parts/collectionview.md:
--------------------------------------------------------------------------------
1 | # CollectionViews
2 |
3 | **Documentation will be available shortly.**
4 |
--------------------------------------------------------------------------------
/docs/img/ReactantTutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/ReactantTutorial.png
--------------------------------------------------------------------------------
/docs/img/ReactantLiveUIDebug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/ReactantLiveUIDebug.png
--------------------------------------------------------------------------------
/docs/img/ReactantLiveUIError.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/ReactantLiveUIError.png
--------------------------------------------------------------------------------
/docs/img/Tutorials/RunButton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/Tutorials/RunButton.png
--------------------------------------------------------------------------------
/docs/img/ReactantLiveUIPreview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/ReactantLiveUIPreview.png
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ReactantPrototyping/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/docs/img/Tutorials/Notes/Simulator1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/Tutorials/Notes/Simulator1.png
--------------------------------------------------------------------------------
/docs/img/Tutorials/Notes/Simulator2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/Tutorials/Notes/Simulator2.png
--------------------------------------------------------------------------------
/docs/img/Tutorials/GitExplorer/Users.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/Tutorials/GitExplorer/Users.png
--------------------------------------------------------------------------------
/docs/img/Tutorials/GitExplorer/Repositories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/Tutorials/GitExplorer/Repositories.png
--------------------------------------------------------------------------------
/docs/img/Tutorials/GitExplorer/UserHeader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/Tutorials/GitExplorer/UserHeader.png
--------------------------------------------------------------------------------
/docs/img/Tutorials/Notes/Reactant-init-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/docs/img/Tutorials/Notes/Reactant-init-console.png
--------------------------------------------------------------------------------
/Tests/Core/Component/Note.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Note.swift
3 | // Reactant-Notes
4 | //
5 | // Created by Matyáš Kříž on 27/10/2017.
6 | //
7 |
8 | import Foundation
9 |
--------------------------------------------------------------------------------
/ReactantPrototyping/Assets.xcassets/falcon.imageset/falcon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Brightify/Reactant/HEAD/ReactantPrototyping/Assets.xcassets/falcon.imageset/falcon.jpg
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Source/Validation/Rules/Rules.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Rules.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 10.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | public struct Rules {
10 | }
11 |
--------------------------------------------------------------------------------
/Example/Reactant.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Reactant.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Source/TableView/TableViewState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewState.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 12.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | public typealias TableViewState = CollectionViewState
10 |
--------------------------------------------------------------------------------
/Source/Validation/ValidationError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ValidationError.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 10.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | public enum ValidationError: Error {
10 | case invalid
11 | }
12 |
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Reactant.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Reactant.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Reactant.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/Application/Sources/DependencyModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DependencyModule.swift
3 | // Reactant
4 | //
5 | // Created by Matous Hybl on 3/24/17.
6 | // Copyright © 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol DependencyModule {
12 | var nameService: NameService { get }
13 | }
14 |
--------------------------------------------------------------------------------
/Source/Configuration/SubConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubConfiguration.swift
3 | // Reactant
4 | //
5 | // Created by Robin Krenecky on 24/04/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | public protocol SubConfiguration {
10 | var configuration: Configuration { get }
11 |
12 | init(configuration: Configuration)
13 | }
14 |
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "landscape",
5 | "idiom" : "tv",
6 | "extent" : "full-screen",
7 | "minimum-system-version" : "9.0",
8 | "scale" : "1x"
9 | }
10 | ],
11 | "info" : {
12 | "version" : 1,
13 | "author" : "xcode"
14 | }
15 | }
--------------------------------------------------------------------------------
/Source/Utils/Recycler/UIView+Reusable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Reusable.swift
3 | // Reactant
4 | //
5 | // Created by Tadeáš Kříž on 1/24/17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView: Reusable {
12 |
13 | public func prepareForReuse() {
14 | removeFromSuperview()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/Utils/UIImage+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+Utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIImage {
12 |
13 | public var aspectRatio: CGFloat {
14 | return size.width / size.height
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/Example/Application/Sources/AppModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppModule.swift
3 | // Reactant
4 | //
5 | // Created by Matous Hybl on 3/24/17.
6 | // Copyright © 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class AppModule: DependencyModule {
12 |
13 | var nameService: NameService {
14 | return NameService()
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/Example/Application/Sources/Service/NameService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NameService.swift
3 | // Reactant
4 | //
5 | // Created by Matous Hybl on 3/24/17.
6 | // Copyright © 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct NameService {
12 |
13 | func names() -> [String] {
14 | return ["Tadeas", "Filip", "Matous"]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/Core/Styling/PercentUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PercentUtils.swift
3 | // Reactant
4 | //
5 | // Created by Tadeas Kriz on 24/01/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | postfix operator %
12 |
13 | /// Returns input / 100.
14 | public postfix func %(input: CGFloat) -> CGFloat {
15 | return input / 100
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '9.0'
2 | use_frameworks!
3 |
4 | target 'Reactant_Example' do
5 | pod 'Reactant', :path => '../'
6 | pod 'Reactant/TableView', :path => '../'
7 | pod 'Reactant/CollectionView', :path => '../'
8 | pod 'Reactant/Validation', :path => '../'
9 |
10 | target 'Reactant_Tests' do
11 | inherit! :search_paths
12 |
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/Source/Validation/Rules/Error/String+EmailValidationError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+EmailValidationError.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 10.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | extension Rules.String {
10 |
11 | public enum EmailValidationError: Error {
12 | case empty
13 | case invalid
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/docs/scripts/zopim.js:
--------------------------------------------------------------------------------
1 | window.$zopim||(function(d,s){var z=$zopim=function(c){z._.push(c)},$=z.s=
2 | d.createElement(s),e=d.getElementsByTagName(s)[0];z.set=function(o){z.set.
3 | _.push(o)};z._=[];z.set._=[];$.async=!0;$.setAttribute("charset","utf-8");
4 | $.src="https://v2.zopim.com/?4o5d8TVw1Gt95sL69RIn0iWT2V4NSeye";z.t=+new Date;$.
5 | type="text/javascript";e.parentNode.insertBefore($,e)})(document,"script");
6 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UILabel+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UILabel+Init.swift
3 | // Reactant
4 | //
5 | // Created by Tadeas Kriz on 31/03/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UILabel {
12 |
13 | public convenience init(text: String) {
14 | self.init()
15 |
16 | self.text = text
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UITableView+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+Init.swift
3 | // Reactant
4 | //
5 | // Created by Tadeas Kriz on 31/03/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITableView {
12 |
13 | public convenience init(style: UITableView.Style) {
14 | self.init(frame: CGRect.zero, style: style)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/Utils/Recycler/Reusable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Reusable.swift
3 | // Reactant
4 | //
5 | // Created by Tadeáš Kříž on 1/24/17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | public protocol Reusable: class {
10 | /**
11 | * Called during recyclation. Usually resets the Component's state.
12 | * NOTE: For more info see `Recycler`.
13 | */
14 | func prepareForReuse()
15 | }
16 |
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/Source/Utils/Scrollable/Scrollable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Scrollable.swift
3 | // Reactant
4 | //
5 | // Created by Matouš Hýbl on 7/13/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | public protocol Scrollable {
10 |
11 | func scrollToTop(animated: Bool)
12 | }
13 |
14 | public extension Scrollable {
15 |
16 | func scrollToTop() {
17 | scrollToTop(animated: true)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/docs/docpress.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": "Brightify/Reactant",
3 | "markdown": {
4 | "typographer": true,
5 | "plugins": {
6 | "decorate": {}
7 | }
8 | },
9 | "plugins": {
10 | "docpress-core": {},
11 | "docpress-base": {}
12 | },
13 | "scripts": [
14 | "scripts/zopim.js"
15 | ],
16 | "googleAnalytics": {
17 | "id": "UA-48374666-17",
18 | "domain": "docs.reactant.tech"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UIFont+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFont+Init.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIFont {
12 |
13 | public convenience init(_ name: String, _ size: CGFloat) {
14 | self.init(descriptor: UIFontDescriptor(name: name, size: size), size: 0)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ReactantPrototyping/Assets.xcassets/falcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "falcon.jpg",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Source/Configuration/Configuration+Style.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Configuration+Style.swift
3 | // Reactant
4 | //
5 | // Created by Robin Krenecky on 24/04/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | public final class StyleConfiguration: BaseSubConfiguration {
10 | }
11 |
12 | extension Configuration {
13 | public var style: StyleConfiguration {
14 | return StyleConfiguration(configuration: self)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/Configuration/BaseSubConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseSubConfiguration.swift
3 | // Reactant
4 | //
5 | // Created by Robin Krenecky on 24/04/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | open class BaseSubConfiguration: SubConfiguration {
10 | public let configuration: Configuration
11 |
12 | public required init(configuration: Configuration) {
13 | self.configuration = configuration
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Source/Core/Properties+Style.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Properties+Style.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | extension Properties.Style {
10 |
11 | public static func style(for type: T.Type, defaultValue: @escaping (T) -> Void = { _ in }) -> Property<(T) -> Void> {
12 | return Property<(T) -> Void>(defaultValue: defaultValue)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UICollectionView+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+Init.swift
3 | // Reactant
4 | //
5 | // Created by Tadeas Kriz on 31/03/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UICollectionView {
12 |
13 | public convenience init(collectionViewLayout layout: UICollectionViewLayout) {
14 | self.init(frame: CGRect.zero, collectionViewLayout: layout)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/Utils/Scrollable/UIScrollView+Scrollable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollView+Scrollable.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 09.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIScrollView: Scrollable {
12 |
13 | public func scrollToTop(animated: Bool) {
14 | let inset = contentInset
15 | setContentOffset(CGPoint(x: -inset.left, y: -inset.top), animated: animated)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Source/Utils/Swift+Compatibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Swift+Compatibility.swift
3 | // Reactant
4 | //
5 | // Created by Matouš Hýbl on 06/04/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | #if !swift(>=4.1)
12 | public extension Sequence {
13 | func compactMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
14 | return try self.flatMap(transform)
15 | }
16 | }
17 |
18 | #endif
19 |
--------------------------------------------------------------------------------
/Source/Utils/Dictionary+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dictionary+Utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Dictionary {
12 |
13 | public init(keyValueTuples: [(Key, Value)]) {
14 | var result: [Key: Value] = [:]
15 | for item in keyValueTuples {
16 | result[item.0] = item.1
17 | }
18 | self = result
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/Source/Core/Controller/DialogDismissalListener.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DialogDismissalListener.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 09.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | public protocol DialogDismissalListener {
10 |
11 | func dialogWillDismiss()
12 |
13 | func dialogDidDismiss()
14 | }
15 |
16 | extension DialogDismissalListener {
17 |
18 | public func dialogWillDismiss() { }
19 |
20 | public func dialogDidDismiss() { }
21 | }
22 |
--------------------------------------------------------------------------------
/Source/Configuration/OptionalType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionalType.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 14.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | /// A helper protocol to allow Property's default value be nil automatically, if its type is optional.
10 | public protocol OptionalType {
11 | static var null: Self { get }
12 | }
13 |
14 | extension Optional: OptionalType {
15 | public static var null: Optional {
16 | return nil
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/CGPoint+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGPoint+Init.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension CGPoint {
12 |
13 | public init(_ both: CGFloat) {
14 | self.init(x: both, y: both)
15 | }
16 |
17 | public init(x: CGFloat) {
18 | self.init(x: x, y: 0)
19 | }
20 |
21 | public init(y: CGFloat) {
22 | self.init(x: 0, y: y)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/UILabel+InitTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UILabel+InitTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class UILabelInitTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("UILabel init") {
17 | it("creates UILabel with text") {
18 | expect(UILabel(text: "text").text) == "text"
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/CGSize+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGSize+Init.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension CGSize {
12 |
13 | public init(_ both: CGFloat) {
14 | self.init(width: both, height: both)
15 | }
16 |
17 | public init(width: CGFloat) {
18 | self.init(width: width, height: 0)
19 | }
20 |
21 | public init(height: CGFloat) {
22 | self.init(width: 0, height: height)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Source/Utils/Array+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 20.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | extension Array {
10 |
11 | public func arrayByAppending(_ elements: Element...) -> Array {
12 | return arrayByAppending(elements)
13 | }
14 |
15 | public func arrayByAppending(_ elements: [Element]) -> Array {
16 | var mutableCopy = self
17 | mutableCopy.append(contentsOf: elements)
18 | return mutableCopy
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Source/Utils/Visibility/Visibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Visibility.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 | import Foundation
9 |
10 | /**
11 | * ## Visible
12 | * View is fully visible with no modifications.
13 | * ## Hidden
14 | * View is not visible, but its dimensions stay the same.
15 | * ## Collapsed
16 | * View is not visible and is squashed on either X or Y axis.
17 | */
18 | @objc
19 | public enum Visibility: Int {
20 | case visible
21 | case hidden
22 | case collapsed
23 | }
24 |
--------------------------------------------------------------------------------
/Example/Application/Sources/Components/Table/TableRootView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableRootView.swift
3 | // Reactant
4 | //
5 | // Created by Matous Hybl on 3/24/17.
6 | // Copyright © 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Reactant
10 |
11 | class TableViewRootView: PlainTableView, RootView {
12 |
13 | override var edgesForExtendedLayout: UIRectEdge {
14 | return .all
15 | }
16 |
17 | init() {
18 | super.init(cellFactory: LabelView.init,
19 | style: .plain,
20 | reloadable: false)
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/Tests/TestUtils/ComponentBase+spy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComponentBase+spy.swift
3 | // ReactantTests
4 | //
5 | // Created by Tadeas Kriz on 07/11/2019.
6 | // Copyright © 2019 Brightify. All rights reserved.
7 | //
8 |
9 | import Reactant
10 | import Cuckoo
11 |
12 | extension ComponentBase {
13 | static func spy(canUpdate: Bool = true) -> MockComponentBase {
14 | return createMock(MockComponentBase.self) { builder, stub in
15 | builder.enableSuperclassSpy()
16 | return MockComponentBase(canUpdate: canUpdate)
17 | }
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UIOffset+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIOffset+Init.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIOffset {
12 |
13 | public init(_ all: CGFloat) {
14 | self.init(horizontal: all, vertical: all)
15 | }
16 |
17 | public init(horizontal: CGFloat) {
18 | self.init(horizontal: horizontal, vertical: 0)
19 | }
20 |
21 | public init(vertical: CGFloat) {
22 | self.init(horizontal: 0, vertical: vertical)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Source/TableView/Properties+TableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Properties+TableView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension Properties.Style {
12 |
13 | public struct TableView {
14 |
15 | public static let tableView = Properties.Style.style(for: ReactantTableView.self)
16 | public static let headerFooterWrapper = Properties.Style.style(for: UITableViewHeaderFooterView.self)
17 | public static let cellWrapper = Properties.Style.style(for: UITableViewCell.self)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/UIFont+InitTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFont+InitTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class UIFontInitTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("UIFont init") {
17 | it("creates UIFont") {
18 | let font = UIFont("HelveticaNeue", 12)
19 |
20 | expect(font.fontName) == "HelveticaNeue"
21 | expect(font.pointSize) == 12
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Source/Utils/Visibility/ConstraintAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConstraintAction.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /**
12 | * ## Set Constant
13 | * Sets constants to `Constraint` in its visible and collapsed form.
14 | * ## Install
15 | * Activates the constraint.
16 | * ## Uninstall
17 | * Deactivates the constraint.
18 | * - NOTE: Deactivated constraint is not taken into account when AutoLayouting.
19 | */
20 | public enum ConstraintAction {
21 | case setConstant(visible: CGFloat, collapsed: CGFloat)
22 | case install
23 | case uninstall
24 | }
25 |
--------------------------------------------------------------------------------
/Source/CollectionView/CollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionViewCell.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 14.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol CollectionViewCell {
12 |
13 | func setSelected(_ selected: Bool)
14 |
15 | func setHighlighted(_ highlighted: Bool)
16 | }
17 |
18 | extension CollectionViewCell {
19 | /// Called after the user lifts the finger after tapping the cell.
20 | public func setSelected(_ selected: Bool) {
21 | }
22 |
23 | /// Called when user taps the cell.
24 | public func setHighlighted(_ highlighted: Bool) {
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Source/TableView/ReactantTableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReactantTableView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol ReactantTableView: class, Scrollable {
12 |
13 | var tableView: UITableView { get }
14 | #if os(iOS)
15 | var refreshControl: UIRefreshControl? { get }
16 | #endif
17 | var emptyLabel: UILabel { get }
18 | var loadingIndicator: UIActivityIndicatorView { get }
19 | }
20 |
21 | extension ReactantTableView {
22 |
23 | public func scrollToTop(animated: Bool) {
24 | tableView.scrollToTop(animated: animated)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Source/Utils/RxUtils/ObserverType+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObserverType+Utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 20.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | public extension ObserverType {
12 |
13 | /**
14 | * Convenience method equivalent to `on(.Next(element: E))` and `on(.Completed)`
15 | * - parameter element: Next element to send to observer(s)
16 | */
17 | func onLast(_ element: Element) {
18 | on(.next(element))
19 | on(.completed)
20 | }
21 | }
22 |
23 | public extension ObserverType where Element == Void {
24 | func onLast() {
25 | onLast(())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Source/Utils/Visibility/CollapseAxis.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollapseAxis.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 | import Foundation
9 |
10 | /**
11 | * Axis on which a **UIView** should collapse.
12 | * ## Horizontal
13 | * **UIView** collapses on horizontal axis (i.e. its width is squashed).
14 | * ## Vertical
15 | * **UIView** collapses on vertical axis (i.e. its height is squashed).
16 | * ## Both
17 | * **UIView** collapses on both axes (i.e. both its weight and height are squashed).
18 | */
19 | @objc
20 | public enum CollapseAxis: Int {
21 | case horizontal
22 | case vertical
23 | case both
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/UITableView+InitTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+InitTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class UITableViewInitTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("UITableView init") {
17 | it("creates UITableView with zero CGRect") {
18 | let view = UITableView(style: .plain)
19 |
20 | expect(view.frame) == CGRect.zero
21 | expect(view.style) == UITableView.Style.plain
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/TVPrototyping/Components/TabController/TabController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabController.swift
3 | // TVPrototyping
4 | //
5 | // Created by Matous Hybl on 03/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Reactant
10 |
11 | class TabController: UITabBarController {
12 |
13 | private let buttonController = ButtonController()
14 | private let tableController = TableViewController()
15 | private let collectionController = CollectionViewController()
16 | private let mapController = MapController()
17 |
18 | override func viewDidLoad() {
19 | setViewControllers([buttonController, tableController, collectionController, mapController], animated: false)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/CGPoint+InitTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGPoint+InitTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class CGPointInitTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("CGPoint init") {
17 | it("creates CGPoint") {
18 | expect(CGPoint(x: 0, y: 0)) == CGPoint()
19 | expect(CGPoint(x: 1, y: 1)) == CGPoint(1)
20 | expect(CGPoint(x: 1, y: 0)) == CGPoint(x: 1)
21 | expect(CGPoint(x: 0, y: 1)) == CGPoint(y: 1)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Source/Utils/UINavigationController+DialogDismissalListener.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UINavigationController+DialogDismissListener.swift
3 | // Reactant
4 | //
5 | // Created by Matouš Hýbl on 29/05/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UINavigationController: DialogDismissalListener {
12 |
13 | public func dialogWillDismiss() {
14 | if let listener = topViewController as? DialogDismissalListener {
15 | listener.dialogWillDismiss()
16 | }
17 | }
18 |
19 | public func dialogDidDismiss() {
20 | if let listener = topViewController as? DialogDismissalListener {
21 | listener.dialogDidDismiss()
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Source/Configuration/Properties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Properties.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 14.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | /// A structure containing all available properties. These are added through extensions.
10 | public struct Properties {
11 |
12 | // Has to be declared here because of https://bugs.swift.org/browse/SR-631 . (Move to Properties+Style when resolved.)
13 | /**
14 | * A structure for style properties. The main difference is that style properties are clusures with one parameter
15 | * which is the styled view.
16 | */
17 | public struct Style {
18 | private init() { }
19 | }
20 |
21 | private init() { }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/StaticMap/MKCoordinateRegion+utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MKCoordinateRegion+utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 26.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import MapKit
10 |
11 | extension MKCoordinateRegion {
12 |
13 | public func inset(percent: Double) -> MKCoordinateRegion {
14 | return inset(horizontalPercent: percent, verticalPercent: percent)
15 | }
16 |
17 | public func inset(horizontalPercent horizontal: Double, verticalPercent vertical: Double) -> MKCoordinateRegion {
18 | return MKCoordinateRegion(
19 | center: center,
20 | span: span.inset(horizontalPercent: horizontal, verticalPercent: vertical))
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/CollectionView/FlowCollectionViewBase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FlowCollectionViewBase.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 13.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FlowCollectionViewBase: CollectionViewBase {
12 |
13 | public let collectionViewLayout = UICollectionViewFlowLayout()
14 |
15 | /// - parameter reloadable: determines whether the **FlowCollectionViewBase** should be reloadable by pulling
16 | public init(reloadable: Bool = true, automaticallyDeselect: Bool = true) {
17 | super.init(layout: collectionViewLayout, reloadable: reloadable, automaticallyDeselect: automaticallyDeselect)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Source/CollectionView/Properties+CollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Properties+CollectionView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension Properties.Style {
12 |
13 | public struct CollectionView {
14 |
15 | public static let collectionView = Properties.Style.style(for: ReactantCollectionView.self)
16 | public static let reusableViewWrapper = Properties.Style.style(for: UICollectionReusableView.self)
17 | public static let cellWrapper = Properties.Style.style(for: UICollectionViewCell.self)
18 | public static let pageControl = Properties.Style.style(for: UIPageControl.self)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Source/StaticMap/MKCoordinateSpan+inset.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MKCoordinateSpan+inset.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 26.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import MapKit
10 |
11 | extension MKCoordinateSpan {
12 |
13 | public func inset(percent: Double) -> MKCoordinateSpan {
14 | return inset(horizontalPercent: percent, verticalPercent: percent)
15 | }
16 |
17 | public func inset(horizontalPercent horizontal: Double, verticalPercent vertical: Double) -> MKCoordinateSpan {
18 | return MKCoordinateSpan(
19 | latitudeDelta: latitudeDelta * (1 + vertical),
20 | longitudeDelta: longitudeDelta * (1 + horizontal)
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/TVPrototyping/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TVPrototyping
4 | //
5 | // Created by Matouš Hýbl on 02/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Reactant
11 | import SnapKit
12 |
13 | @UIApplicationMain
14 | class AppDelegate: UIResponder, UIApplicationDelegate {
15 |
16 | var window: UIWindow?
17 |
18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
19 | let window = UIWindow()
20 | self.window = window
21 | window.backgroundColor = .white
22 | window.rootViewController = TabController()
23 | window.makeKeyAndVisible()
24 | return true
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | Carthage
26 | # We recommend against adding the Pods directory to your .gitignore. However
27 | # you should judge for yourself, the pros and cons are mentioned at:
28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
29 | #
30 | # Note: if you ignore the Pods directory, make sure to uncomment
31 | # `pod install` in .travis.yml
32 | #
33 | Pods/
34 | _docpress
35 | .idea
36 | *.xcworkspace
37 |
--------------------------------------------------------------------------------
/Example/Application/Sources/Components/Main/LabelView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LabelView.swift
3 | // Reactant
4 | //
5 | // Created by Matous Hybl on 3/17/17.
6 | // Copyright © 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Reactant
10 |
11 | final class LabelView: ViewBase {
12 |
13 | private let label = UILabel()
14 |
15 | override func update() {
16 | label.text = componentState
17 | }
18 |
19 | override func loadView() {
20 | children(
21 | label
22 | )
23 |
24 | label.textColor = .black
25 | label.textAlignment = .center
26 | }
27 |
28 | override func setupConstraints() {
29 | label.snp.makeConstraints { make in
30 | make.edges.equalToSuperview()
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/AttributeTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttributeTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class AttributeTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("toDictionary") {
17 | it("creates dictionary from array of attributes") {
18 | let attributes = [Attribute.baselineOffset(1), Attribute.expansion(2)].toDictionary()
19 |
20 | expect(attributes[NSAttributedString.Key.baselineOffset] as? Float) == 1
21 | expect(attributes[NSAttributedString.Key.expansion] as? Float) == 2
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ReactantPrototyping/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ReactantPrototyping
4 | //
5 | // Created by Filip Dolnik on 16.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
17 | let window = UIWindow()
18 | self.window = window
19 | window.backgroundColor = .white
20 | window.rootViewController = UINavigationController(rootViewController: ViewController())
21 | window.makeKeyAndVisible()
22 | return true
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/UICollectionView+InitTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+InitTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class UICollectionViewInitTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("UICollectionView init") {
17 | it("creates UICollectionView with zero CGRect") {
18 | let layout = UICollectionViewFlowLayout()
19 | let view = UICollectionView(collectionViewLayout: layout)
20 |
21 | expect(view.frame) == CGRect.zero
22 | expect(view.collectionViewLayout) == layout
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/UIOffset+InitTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIOffset+InitTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class UIOffsetInitTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("UIOffset init") {
17 | it("creates UIOffset") {
18 | expect(UIOffset(horizontal: 0, vertical: 0)) == UIOffset()
19 | expect(UIOffset(horizontal: 1, vertical: 1)) == UIOffset(1)
20 | expect(UIOffset(horizontal: 1, vertical: 0)) == UIOffset(horizontal: 1)
21 | expect(UIOffset(horizontal: 0, vertical: 1)) == UIOffset(vertical: 1)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/CGSize+InitTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGSize+InitTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class CGSizeInitTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("CGSize init") {
17 | it("creates CGSize") {
18 | expect(CGSize(width: 0 as CGFloat, height: 0 as CGFloat)) == CGSize()
19 | expect(CGSize(width: 0, height: 0)) == CGSize()
20 | expect(CGSize(width: 1, height: 1)) == CGSize(1)
21 | expect(CGSize(width: 1, height: 0)) == CGSize(width: 1)
22 | expect(CGSize(width: 0, height: 1)) == CGSize(height: 1)
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Source/Core/View/RootView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 09.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol RootView {
12 |
13 | var edgesForExtendedLayout: UIRectEdge { get }
14 |
15 | func viewWillAppear()
16 |
17 | func viewDidAppear()
18 |
19 | func viewWillDisappear()
20 |
21 | func viewDidDisappear()
22 | }
23 |
24 | extension RootView {
25 |
26 | public var edgesForExtendedLayout: UIRectEdge {
27 | return []
28 | }
29 |
30 | public func viewWillAppear() {
31 | }
32 |
33 | public func viewDidAppear() {
34 | }
35 |
36 | public func viewWillDisappear() {
37 | }
38 |
39 | public func viewDidDisappear() {
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Example/Tests/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Source/Utils/Recycler/SynchronizedRecycler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SynchronizedRecycler.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class SynchronizedRecycler: Recycler {
12 |
13 | private let syncQueue = DispatchQueue(label: "SynchronizedRecycler_syncQueue")
14 |
15 | public override func obtain() -> T {
16 | return syncQueue.sync {
17 | return super.obtain()
18 | }
19 | }
20 |
21 | public override func recycle(_ instance: T) {
22 | syncQueue.sync {
23 | super.recycle(instance)
24 | }
25 | }
26 |
27 | public override func recycleAll() {
28 | syncQueue.sync {
29 | super.recycleAll()
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/TVPrototyping/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "size" : "1280x768",
5 | "idiom" : "tv",
6 | "filename" : "App Icon - App Store.imagestack",
7 | "role" : "primary-app-icon"
8 | },
9 | {
10 | "size" : "400x240",
11 | "idiom" : "tv",
12 | "filename" : "App Icon.imagestack",
13 | "role" : "primary-app-icon"
14 | },
15 | {
16 | "size" : "2320x720",
17 | "idiom" : "tv",
18 | "filename" : "Top Shelf Image Wide.imageset",
19 | "role" : "top-shelf-image-wide"
20 | },
21 | {
22 | "size" : "1920x720",
23 | "idiom" : "tv",
24 | "filename" : "Top Shelf Image.imageset",
25 | "role" : "top-shelf-image"
26 | }
27 | ],
28 | "info" : {
29 | "version" : 1,
30 | "author" : "xcode"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/PercentUtilsTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PercentUtilsTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class PercentUtilsTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("%") {
17 | it("returns percents") {
18 | expect(35%).to(beCloseTo(0.35, within: 0.35.ulp))
19 | }
20 | it("handles edge cases") {
21 | expect(0%).to(beCloseTo(0.0, within: 0.0.ulp))
22 | expect(100%).to(beCloseTo(1.0, within: Double.ulpOfOne))
23 | expect(-20%).to(beCloseTo(-0.2, within: 0.2.ulp))
24 | expect(1058%).to(beCloseTo(10.58, within: 10.58.ulp))
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Example/Tests/Tests.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import XCTest
3 | import Reactant
4 |
5 | class Tests: XCTestCase {
6 |
7 | override func setUp() {
8 | super.setUp()
9 | // Put setup code here. This method is called before the invocation of each test method in the class.
10 | }
11 |
12 | override func tearDown() {
13 | // Put teardown code here. This method is called after the invocation of each test method in the class.
14 | super.tearDown()
15 | }
16 |
17 | func testExample() {
18 | // This is an example of a functional test case.
19 | XCTAssert(true, "Pass")
20 | }
21 |
22 | func testPerformanceExample() {
23 | // This is an example of a performance test case.
24 | self.measure() {
25 | // Put the code you want to measure the time of here.
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/Source/CollectionView/ReactantCollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReactantCollectionView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol ReactantCollectionView: class, Scrollable {
12 |
13 | var collectionView: UICollectionView { get }
14 | #if os(iOS)
15 | var refreshControl: UIRefreshControl? { get }
16 | #endif
17 | var emptyLabel: UILabel { get }
18 | var loadingIndicator: UIActivityIndicatorView { get }
19 | }
20 |
21 | extension ReactantCollectionView {
22 |
23 | /**
24 | * Scroll the Reactant Collection View to top.
25 | * - parameter animated: determines whether the scroll to top is animated
26 | */
27 | public func scrollToTop(animated: Bool) {
28 | collectionView.scrollToTop(animated: animated)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.6
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Source/Core/Component/ReactantUI.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public protocol ReactantUI: class {
4 | var __rui: ReactantUIContainer { get }
5 | }
6 |
7 | public protocol ReactantUIContainer: class {
8 | var xmlPath: String { get }
9 |
10 | var typeName: String { get }
11 |
12 | func setupReactantUI()
13 |
14 | func updateReactantUI()
15 |
16 | static func destroyReactantUI(target: UIView)
17 | }
18 |
19 | internal extension UIView {
20 | func tryUpdateReactantUI() {
21 | guard self is ReactantUI else {
22 | return
23 | }
24 |
25 | updateReactantUIRecursive()
26 | }
27 |
28 | private func updateReactantUIRecursive() {
29 | if let reactantUi = self as? ReactantUI {
30 | reactantUi.__rui.updateReactantUI()
31 | }
32 |
33 | for child in subviews {
34 | child.updateReactantUIRecursive()
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/CGAffineTransform+Shortcut.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGAffineTransform+Shortcut.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public func + (lhs: CGAffineTransform, rhs: CGAffineTransform) -> CGAffineTransform {
12 | return lhs.concatenating(rhs)
13 | }
14 |
15 | public func rotate(degrees: CGFloat) -> CGAffineTransform {
16 | return rotate(degrees / 180 * .pi)
17 | }
18 |
19 | public func rotate(_ radians: CGFloat = 0) -> CGAffineTransform {
20 | return CGAffineTransform(rotationAngle: radians)
21 | }
22 |
23 | public func translate(x: CGFloat = 0, y: CGFloat = 0) -> CGAffineTransform {
24 | return CGAffineTransform(translationX: x, y: y)
25 | }
26 |
27 | public func scale(x: CGFloat = 1, y: CGFloat = 1) -> CGAffineTransform {
28 | return CGAffineTransform(scaleX: x, y: y)
29 | }
30 |
--------------------------------------------------------------------------------
/TVPrototyping/Components/StaticMap/MapController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapController.swift
3 | // TVPrototyping
4 | //
5 | // Created by Matous Hybl on 03/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Reactant
10 | import MapKit
11 |
12 | final class MapController: ControllerBase {
13 |
14 | override func afterInit() {
15 | tabBarItem = UITabBarItem(title: "StaticMap", image: nil, tag: 0)
16 | }
17 | }
18 |
19 | final class MapRootView: ViewBase, RootView {
20 |
21 | private let map = StaticMap()
22 |
23 | override func update() {
24 | map.componentState = MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake(40.0, 14.0), 10000, 10000)
25 | }
26 |
27 | override func loadView() {
28 | children(map)
29 |
30 | map.snp.makeConstraints { make in
31 | make.edges.equalToSuperview()
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Source/Core/Wireframe/Internal/FutureControllerProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FutureControllerProvider.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 09.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public final class FutureControllerProvider {
12 |
13 | /**
14 | * The get-only controller that provider is working with.
15 | *
16 | * Thanks to generics you can access all the methods you would expect from any subclass of **UIViewController**.
17 | */
18 | public internal(set) weak var controller: T?
19 |
20 | /**
21 | * Controller's navigation controller. Useful for not having to pass UINavigationController around.
22 | *
23 | * - NOTE: See also **UINavigationController+Navigation.swift** for useful methods.
24 | */
25 | public var navigation: UINavigationController? {
26 | return controller?.navigationController
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Documentation
2 |
3 | * [Reactant](../README.md)
4 |
5 | * Getting started
6 | * [Quick-start guide](getting-started/quickstart.md)
7 | * [Architecture](getting-started/architecture.md)
8 | * [Troubleshooting](getting-started/troubleshooting.md)
9 |
10 | * Tutorials
11 | * [Simple note taking app](tutorials/notes.md)
12 | * [Random GitHub users explorer](tutorials/explorer.md)
13 |
14 | * Reactant UI
15 | * [Introduction](reactant-ui/introduction.md)
16 | * [Live Reload](reactant-ui/live-reload.md)
17 |
18 | * Parts
19 | * [Configuration](parts/configuration.md)
20 | * [Controller](parts/controller.md)
21 | * [RootView](parts/rootview.md)
22 | * [Wireframe](parts/wireframe.md)
23 | * [Styling](parts/styling.md)
24 | * [TableView](parts/tableview.md)
25 | * [CollectionView](parts/collectionview.md)
26 | * [Validation](parts/validation.md)
27 | * [ActivityIndicator](parts/activityindicator.md)
28 | * [StaticMap](parts/staticmap.md)
29 |
--------------------------------------------------------------------------------
/TVPrototyping/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIRequiredDeviceCapabilities
24 |
25 | arm64
26 |
27 | UIUserInterfaceStyle
28 | Automatic
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Example/Application/Resources/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | }
43 | ],
44 | "info" : {
45 | "version" : 1,
46 | "author" : "xcode"
47 | }
48 | }
--------------------------------------------------------------------------------
/Example/Application/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Reactant
4 | //
5 | // Created by matoushybl on 03/16/2017.
6 | // Copyright (c) 2017 matoushybl. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Reactant
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | window = UIWindow(frame: UIScreen.main.bounds)
19 | window?.backgroundColor = .white
20 | window?.makeKeyAndVisible()
21 |
22 | Configuration.global.set(Properties.Style.controllerRoot) {
23 | $0.backgroundColor = .white
24 | }
25 |
26 | let module = AppModule()
27 |
28 | let wireframe = MainWireframe(dependencyModule: module)
29 |
30 | window?.rootViewController = wireframe.entrypoint()
31 | return true
32 | }
33 |
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/Tests/Core/Component/ComponentTestParts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComponentTestParts.swift
3 | // ReactantTests
4 | //
5 | // Created by Matyáš Kříž on 16/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | enum ComponentTestAction {
10 | case none
11 | case one
12 | case two
13 | case three
14 | }
15 |
16 | struct ComponentTestState {
17 | let primitive: Int
18 | var tuple: (String, Int)
19 | var classy: ComponentTestClass?
20 |
21 | func something() {
22 | return
23 | }
24 | }
25 |
26 | final class ComponentTestClass {
27 | let primitive: Int
28 | var tuple: (String, Int)
29 | var structy: ComponentTestState
30 |
31 | init(primitive: Int = 12, structy: Bool = false) {
32 | self.primitive = primitive
33 | tuple = ("tup", 10)
34 | self.structy = ComponentTestState(primitive: primitive, tuple: tuple, classy: structy ? ComponentTestClass(primitive: primitive) : nil)
35 | }
36 |
37 | func something() {
38 | return
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://cdn.cocoapods.org/'
2 | use_frameworks!
3 | inhibit_all_warnings!
4 |
5 | def shared
6 | pod 'RxSwift', '~> 5.0'
7 | pod 'RxCocoa', '~> 5.0'
8 | pod 'RxDataSources', '~> 4.0'
9 | pod 'RxOptional', '~> 4.0'
10 | pod 'SnapKit', '~> 5.0'
11 | pod 'Kingfisher', '~> 5.0'
12 | end
13 |
14 | target 'Reactant' do
15 | platform :ios, '10.0'
16 |
17 | shared
18 | end
19 |
20 | target 'ReactantTests' do
21 | platform :ios, '10.0'
22 |
23 | shared
24 |
25 | pod 'Quick', '~> 2.0'
26 | pod 'Nimble', '~> 7.0'
27 | pod 'Cuckoo', :git => 'https://github.com/Brightify/Cuckoo.git', :branch => 'master'
28 | pod 'RxNimble'
29 | pod 'RxTest'
30 | end
31 |
32 | target 'ReactantPrototyping' do
33 | platform :ios, '10.0'
34 |
35 | shared
36 |
37 | pod 'Reactant', :subspecs => ['All-iOS'], :path => './'
38 | end
39 |
40 | target 'TVPrototyping' do
41 | platform :tvos, '10.0'
42 | shared
43 |
44 | pod 'Reactant', :subspecs => ['All-tvOS', 'FallbackSafeAreaInsets'], :path => './'
45 | end
46 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UIButton+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIButton+Utils.swift
3 | // Reactant
4 | //
5 | // Created by Tadeas Kriz on 31/03/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIButton {
12 |
13 | public convenience init(title: String) {
14 | self.init()
15 |
16 | setTitle(title, for: UIControl.State())
17 | }
18 | }
19 |
20 | extension UIButton {
21 |
22 | @objc(setBackgroundColor:forState:)
23 | public func setBackgroundColor(_ color: UIColor, for state: UIControl.State) {
24 | let rectangle = CGRect(size: CGSize(1));
25 | UIGraphicsBeginImageContext(rectangle.size);
26 |
27 | let context = UIGraphicsGetCurrentContext();
28 | context?.setFillColor(color.cgColor);
29 | context?.fill(rectangle);
30 |
31 | let image = UIGraphicsGetImageFromCurrentImageContext();
32 | UIGraphicsEndImageContext();
33 |
34 | setBackgroundImage(image!, for: state)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Source/CollectionView/Identifier/AnyCollectionViewCellIdentifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnyCollectionViewCellIdentifier.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct AnyCollectionViewCellIdentifier {
12 |
13 | internal let name: String
14 | internal let type: UICollectionViewCell.Type
15 | }
16 |
17 | extension CollectionViewCellIdentifier {
18 |
19 | public func typeErased() -> AnyCollectionViewCellIdentifier {
20 | return AnyCollectionViewCellIdentifier(name: name, type: CollectionViewCellWrapper.self)
21 | }
22 | }
23 |
24 | extension UICollectionView {
25 |
26 | public func register(identifier: AnyCollectionViewCellIdentifier) {
27 | register(identifier.type, forCellWithReuseIdentifier: identifier.name)
28 | }
29 |
30 | public func unregister(identifier: AnyCollectionViewCellIdentifier) {
31 | register(nil as AnyClass?, forCellWithReuseIdentifier: identifier.name)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/UIColor+InitTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+InitTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class UIColorInitTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("UIColor init") {
17 | it("accept rgb hex as String") {
18 | expect(UIColor(hex: "#FFFFFF")) == UIColor(red: 1, green: 1, blue: 1, alpha: 1)
19 | }
20 | it("accept rgba hex as String") {
21 | expect(UIColor(hex: "#00FFFF00")) == UIColor(red: 0, green: 1, blue: 1, alpha: 0)
22 | }
23 | it("accept rgb hex as Int") {
24 | expect(UIColor(rgb: 0xFFFFFF)) == UIColor(red: 1, green: 1, blue: 1, alpha: 1)
25 | }
26 | it("accept rgba hex as Int") {
27 | expect(UIColor(rgba: 0x00FFFF00)) == UIColor(red: 0, green: 1, blue: 1, alpha: 0)
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docs/parts/staticmap.md:
--------------------------------------------------------------------------------
1 | # StaticMap
2 |
3 | StaticMap is one of Reactant's prebuilt components. To integrate it into your project, you need to add `StaticMap` subspec to your `Podfile`. To do so, open your `Podfile` in a text editor and add the following line to your application's target:
4 |
5 | ```ruby
6 | pod 'Reactant/StaticMap'
7 | ```
8 |
9 | `StaticMap` is meant to be used wherever you want to display a map without letting the user control it. Were you to use the `MKMapView`, you would get noticeable lags when pushing a controller as `MKMapView` renders on main thread. `StaticMap` renders in background and presents a loaded image.
10 |
11 | To render the map, we recommend using `MKMapSnapshotter`, and to cache the map we recommend a library called `Kingfisher`.
12 |
13 | `StaticMap` has a `componentState` of type `MKCoordinateRegion`. This region specifies the visible part of the map. It also has an `action` of type `StaticMapAction`. This action is just an enum, having a single case `selected`. Whenever user taps on the map, `StaticMap` sends this `.selected` action to be handled.
14 |
--------------------------------------------------------------------------------
/Source/Configuration/Configurable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Configurable.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 14.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | public protocol Configurable: class {
10 | /// See `Configuration` for more info.
11 | var configuration: Configuration { get set }
12 | }
13 |
14 | extension Configurable {
15 |
16 | /**
17 | * Reloads object's configuration. Essentially just calls `didSet` on its configuration.
18 | */
19 | public func reloadConfiguration() {
20 | let temp = configuration
21 | configuration = temp
22 | }
23 |
24 | /**
25 | * Applies passed `Configuration` to this object.
26 | * This function is destructive, but also returns itself to allow chaining.
27 | * - parameter configuration: new configuration to set to the object
28 | * - returns: self with new configuration set
29 | */
30 | public func with(configuration: Configuration) -> Self {
31 | self.configuration = configuration
32 | return self
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/parts/validation.md:
--------------------------------------------------------------------------------
1 | # Validation
2 |
3 | Validation classes can be used for an easy validation of user input such as emails or passwords.
4 |
5 | A good example of creating Rules for validation is in `Rules.String` class. Let's have a look at the `minLength` Rule.
6 |
7 | ```swift
8 | public static func minLength(_ length: Int) -> Rule {
9 | return Rule { value in
10 | guard let value = value, value.count >= length else {
11 | return .invalid
12 | }
13 | return nil
14 | }
15 | }
16 | ```
17 |
18 | If the condition for valid string is not true, then this code returns `ValidationError.invalid`, `nil` otherwise.
19 |
20 | Usage of this rule can look like this, when we use it as a part of `Observable` stream.
21 |
22 | ```swift
23 | Observable.from(["this", "is", "a", "message"])
24 | .map { Rules.String.minLength(4).run($0) }
25 | .filterError()
26 | .subscribe(onNext: { print($0 ?? "") })
27 | .disposed(by: stateDisposeBag)
28 | ```
29 |
30 | This code prints only `this` `message` - the strings that are valid.
31 |
--------------------------------------------------------------------------------
/Source/Configuration/Property.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Property.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 14.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | * A definition of a configurable property. It defines its type and a default value.
13 | */
14 | public struct Property {
15 |
16 | public let id: Int
17 | public let defaultValue: T
18 |
19 | public init(defaultValue: T) {
20 | id = PropertyIdProvider.nextId()
21 | self.defaultValue = defaultValue
22 | }
23 | }
24 |
25 | extension Property where T: OptionalType {
26 | public init() {
27 | self.init(defaultValue: T.null)
28 | }
29 | }
30 |
31 | private struct PropertyIdProvider {
32 |
33 | private static var lastUsedId = -1
34 |
35 | private static let syncQueue = DispatchQueue(label: "PropertyIdProvider_syncQueue")
36 |
37 | fileprivate static func nextId() -> Int {
38 | return syncQueue.sync {
39 | lastUsedId += 1
40 | return lastUsedId
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UITabBarController+Styles.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITabBarController+Styles.swift
3 | // Reactant
4 | //
5 | // Created by Matouš Hýbl on 23/02/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITabBarController: Styleable { }
12 |
13 | extension UITabBarController {
14 |
15 | public func apply(style: Style) {
16 | style(tabBar)
17 | }
18 |
19 | public func apply(styles: Style...) {
20 | styles.forEach(apply(style:))
21 | }
22 |
23 | public func apply(styles: [Style]) {
24 | styles.forEach(apply(style:))
25 | }
26 |
27 | public func styled(using styles: Style...) -> Self {
28 | styles.forEach(apply(style:))
29 | return self
30 | }
31 |
32 | public func styled(using styles: [Style]) -> Self {
33 | apply(styles: styles)
34 | return self
35 | }
36 |
37 | public func with(_ style: Style) -> Self {
38 | apply(style: style)
39 | return self
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Brightify.org
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Source/Utils/AssociatedObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AssociatedObject.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public func associatedObject(_ base: Any, key: UnsafePointer, defaultValue: @autoclosure () -> T) -> T {
12 | if let associated = objc_getAssociatedObject(base, key) as? T {
13 | return associated
14 | } else {
15 | let value = defaultValue()
16 | associateObject(base, key: key, value: value)
17 | return value
18 | }
19 | }
20 |
21 | public func associatedObject(_ base: Any, key: UnsafePointer, initializer: () -> T) -> T {
22 | if let associated = objc_getAssociatedObject(base, key) as? T {
23 | return associated
24 | } else {
25 | let defaultValue = initializer()
26 | associateObject(base, key: key, value: defaultValue)
27 | return defaultValue
28 | }
29 | }
30 |
31 | public func associateObject(_ base: Any, key: UnsafePointer, value: T) {
32 | objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
33 | }
34 |
--------------------------------------------------------------------------------
/Example/Application/Sources/Components/Table/TableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewController.swift
3 | // Reactant
4 | //
5 | // Created by Matous Hybl on 3/24/17.
6 | // Copyright © 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Reactant
10 |
11 | class TableViewController: ControllerBase {
12 |
13 | struct Dependencies {
14 | let nameService: NameService
15 | }
16 |
17 | struct Reactions {
18 | let displayName: (String) -> Void
19 | }
20 |
21 | private let dependencies: Dependencies
22 | private let reactions: Reactions
23 |
24 | init(dependencies: Dependencies, reactions: Reactions) {
25 | self.dependencies = dependencies
26 | self.reactions = reactions
27 | super.init()
28 | }
29 |
30 | override func update() {
31 | rootView.componentState = .items(dependencies.nameService.names())
32 | }
33 |
34 | override func act(on action: TableViewRootView.ActionType) {
35 | switch action {
36 | case .selected(let name):
37 | reactions.displayName(name)
38 | default:
39 | break
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UINavigationController+Styles.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UINavigationController+Styles.swift
3 | // Reactant
4 | //
5 | // Created by Matouš Hýbl on 23/02/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UINavigationController: Styleable { }
12 |
13 | extension UINavigationController {
14 |
15 | public func apply(style: Style) {
16 | style(navigationBar)
17 | }
18 |
19 | public func apply(styles: Style...) {
20 | styles.forEach(apply(style:))
21 | }
22 |
23 | public func apply(styles: [Style]) {
24 | styles.forEach(apply(style:))
25 | }
26 |
27 | public func styled(using styles: Style...) -> Self {
28 | styles.forEach(apply(style:))
29 | return self
30 | }
31 |
32 | public func styled(using styles: [Style]) -> Self {
33 | apply(styles: styles)
34 | return self
35 | }
36 |
37 | public func with(_ style: Style) -> Self {
38 | apply(style: style)
39 | return self
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Tests/Configuration/PropertyTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PropertyTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 26.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class PropertyTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("Property") {
17 | describe("init") {
18 | it("assigns unique id") {
19 | let property1 = Property(defaultValue: 0)
20 | let property2 = Property(defaultValue: 0)
21 |
22 | expect(property1.id) != property2.id
23 | }
24 | it("sets defaultValue") {
25 | let property = Property(defaultValue: 1)
26 |
27 | expect(property.defaultValue) == 1
28 | }
29 | it("sets defaultValue to nil for Optional types") {
30 | let property = Property()
31 |
32 | expect(property.defaultValue).to(beNil())
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Source/Utils/Hash.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Hash.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 10.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | private let djbHashInitialValue = 5381
12 |
13 | private func computeDjbHash(accumulator: Int, hashValue: Int) -> Int {
14 | return (accumulator << 5) &+ accumulator &+ hashValue
15 | }
16 |
17 | extension Sequence where Iterator.Element == Int {
18 |
19 | public func djbHash() -> Int {
20 | return reduce(djbHashInitialValue, computeDjbHash)
21 | }
22 | }
23 |
24 | extension Sequence where Iterator.Element == Int? {
25 |
26 | public func djbHash() -> Int {
27 | return reduce(djbHashInitialValue) {
28 | if let hashValue = $1 {
29 | return computeDjbHash(accumulator: $0, hashValue: hashValue)
30 | } else {
31 | return $0
32 | }
33 | }
34 | }
35 | }
36 |
37 | extension Sequence where Iterator.Element: Hashable {
38 |
39 | public func djbHash() -> Int {
40 | return reduce(djbHashInitialValue) {
41 | computeDjbHash(accumulator: $0, hashValue: $1.hashValue)
42 | }
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/Source/Utils/UIStackView+ArrangedChildren.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIStackView+ArrangedChildren.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 20.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @available(iOS 9.0, *)
12 | extension UIStackView {
13 |
14 | /**
15 | * Adds arranged subviews to the stack view that this method is called upon.
16 | * Convenience method working the same as `addArrangedSubview(_:)` but letting you pass multiple UIViews at once.
17 | * - parameter children: `UIView`s to be added as arranged subviews
18 | */
19 | @discardableResult
20 | public func arrangedChildren(_ children: UIView...) -> UIStackView {
21 | return arrangedChildren(children)
22 | }
23 |
24 | /**
25 | * Adds arranged subviews to the stack view that this method is called upon.
26 | * Convenience method working the same as `addArrangedSubview(_:)` but letting you pass an array of UIViews.
27 | * - parameter children: `UIView`s to be added as arranged subviews
28 | */
29 | @discardableResult
30 | public func arrangedChildren(_ children: [UIView]) -> UIStackView {
31 | children.forEach(addArrangedSubview)
32 | return self
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Example/Application/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Reactant
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Source/Utils/Collection+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Collection+Utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Collection {
12 |
13 | public func groupBy(_ extractKey: (Iterator.Element) -> KEY) -> [(KEY, [Iterator.Element])] {
14 | return groupBy { Optional(extractKey($0)) }
15 | }
16 |
17 | public func groupBy(_ extractKey: (Iterator.Element) -> KEY?) -> [(KEY, [Iterator.Element])] {
18 | var grouped: [(KEY, [Iterator.Element])] = []
19 | var t: [String] = []
20 | func add(_ item: Iterator.Element, forKey key: KEY) {
21 | if let index = grouped.firstIndex(where: { $0.0 == key }) {
22 | let value = grouped[index]
23 | grouped[index] = (key, value.1.arrayByAppending(item))
24 | } else {
25 | grouped.append((key, [item]))
26 | }
27 | }
28 |
29 | for item in self {
30 | guard let key = extractKey(item) else {
31 | continue
32 | }
33 | add(item, forKey: key)
34 | }
35 | return grouped
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/TVPrototyping/Components/TableView/TableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableController.swift
3 | // TVPrototyping
4 | //
5 | // Created by Matous Hybl on 03/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Reactant
10 |
11 | final class TableViewController: ControllerBase {
12 |
13 | override func afterInit() {
14 | rootView.componentState = .items(["Cell 1", "Cell 2", "Cell 3", "Cell 4"])
15 |
16 | tabBarItem = UITabBarItem(title: "TableView", image: nil, tag: 0)
17 | }
18 |
19 | }
20 |
21 | final class TableViewRootView: PlainTableView {
22 |
23 | @objc
24 | init() {
25 | super.init(cellFactory: TestCell.init, style: .plain, reloadable: false)
26 |
27 | tableView.rowHeight = UITableViewAutomaticDimension
28 | tableView.estimatedRowHeight = 150
29 | }
30 | }
31 |
32 | final class TestCell: ViewBase {
33 | private let label = UILabel()
34 |
35 | override func update() {
36 | label.text = componentState
37 | }
38 |
39 | override func loadView() {
40 | children(label)
41 | }
42 |
43 | override func setupConstraints() {
44 | label.snp.makeConstraints { make in
45 | make.edges.equalToSuperview().inset(50)
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Source/TableView/Identifier/TableViewHeaderFooterIdentifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewHeaderFooterIdentifier.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct TableViewHeaderFooterIdentifier {
12 |
13 | internal let name: String
14 |
15 | public init(name: String = NSStringFromClass(T.self)) {
16 | self.name = name
17 | }
18 | }
19 |
20 | extension UITableView {
21 |
22 | public func register(identifier: TableViewHeaderFooterIdentifier) {
23 | register(TableViewHeaderFooterWrapper.self, forHeaderFooterViewReuseIdentifier: identifier.name)
24 | }
25 |
26 | public func unregister(identifier: TableViewHeaderFooterIdentifier) {
27 | register(nil as AnyClass?, forHeaderFooterViewReuseIdentifier: identifier.name)
28 | }
29 | }
30 |
31 | extension UITableView {
32 |
33 | public func dequeue(identifier: TableViewHeaderFooterIdentifier) -> TableViewHeaderFooterWrapper {
34 | guard let view = dequeueReusableHeaderFooterView(withIdentifier: identifier.name) as? TableViewHeaderFooterWrapper else {
35 | fatalError("\(identifier) is not registered.")
36 | }
37 | return view
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Source/CollectionView/Identifier/AnyCollectionSupplementaryViewIdentifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnyCollectionSupplementaryViewIdentifier.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct AnyCollectionSupplementaryViewIdentifier {
12 |
13 | internal typealias IdentifiedType = UICollectionReusableView
14 |
15 | internal var name: String
16 | internal var kind: String
17 | internal var type: UICollectionReusableView.Type
18 | }
19 |
20 | extension CollectionSupplementaryViewIdentifier {
21 |
22 | public func typeErased() -> AnyCollectionSupplementaryViewIdentifier {
23 | return AnyCollectionSupplementaryViewIdentifier(name: name, kind: kind, type: CollectionReusableViewWrapper.self)
24 | }
25 | }
26 |
27 | extension UICollectionView {
28 |
29 | public func register(identifier: AnyCollectionSupplementaryViewIdentifier) {
30 | register(identifier.type, forSupplementaryViewOfKind: identifier.kind, withReuseIdentifier: identifier.name)
31 | }
32 |
33 | public func unregister(identifier: AnyCollectionSupplementaryViewIdentifier) {
34 | register(nil as AnyClass?, forSupplementaryViewOfKind: identifier.kind, withReuseIdentifier: identifier.name)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * http://www.objc.io/issue-6/travis-ci.html
3 | # * https://github.com/supermarin/xcpretty#usage
4 |
5 | osx_image: xcode10.2
6 | language: objective-c
7 | # cache: cocoapods
8 | # podfile: Example/Podfile
9 | # before_install:
10 | # - gem install cocoapods # Since Travis is not always on latest version
11 | # - pod install --project-directory=Example
12 | before_install:
13 | - nvm install 4
14 | # - gem install cocoapods -v '1.2.1'
15 | - pod repo update --silent
16 |
17 | script:
18 | - (xcodebuild -workspace Reactant.xcworkspace -scheme Reactant -sdk iphonesimulator build-for-testing | egrep -A 3 "(error|warning|note):\ "; exit ${PIPESTATUS[0]})
19 | - xctool -workspace Reactant.xcworkspace -scheme Reactant -sdk iphonesimulator run-tests
20 | # - travis_wait pod lib lint --allow-warnings
21 | - npm install docpress && ./node_modules/.bin/docpress build
22 | - cp CNAME ./_docpress/CNAME
23 |
24 | after_success:
25 | - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm install git-update-ghpages && ./node_modules/.bin/git-update-ghpages -e; fi
26 |
27 | env:
28 | global:
29 | - GIT_NAME: Travis CI
30 | - GIT_EMAIL: info@brightify.org
31 | - GITHUB_REPO: Brightify/Reactant
32 | - GIT_SOURCE: _docpress
33 |
34 | notifications:
35 | slack: brightify:00rIGJIfWqG5RyWCVoRNEgxt
36 |
--------------------------------------------------------------------------------
/Source/StaticMap/CLLocationCoordinate2D+utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocationCoordinate2D+utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 26.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import MapKit
10 |
11 | extension CLLocationCoordinate2D {
12 |
13 | public static func startAndEnd(region: MKCoordinateRegion) -> (start: CLLocationCoordinate2D, end: CLLocationCoordinate2D) {
14 | let center = region.center
15 | let centerLat = center.latitude
16 | let centerLon = center.longitude
17 | let span = region.span
18 | let latDelta = span.latitudeDelta
19 | let lonDelta = span.longitudeDelta
20 | let start = CLLocationCoordinate2D(latitude: centerLat - (latDelta / 2), longitude: centerLon - (lonDelta / 2))
21 | let end = CLLocationCoordinate2D(latitude: centerLat + (latDelta / 2), longitude: centerLon + (lonDelta / 2))
22 |
23 | return (start, end)
24 | }
25 | }
26 |
27 | extension CLLocationCoordinate2D: Hashable {
28 | public func hash(into hasher: inout Hasher) {
29 | hasher.combine(latitude)
30 | hasher.combine(longitude)
31 | }
32 | }
33 |
34 | public func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
35 | return lhs.latitude.equal(to: rhs.latitude) && lhs.longitude.equal(to: rhs.longitude)
36 | }
37 |
--------------------------------------------------------------------------------
/Example/Application/Sources/Components/Main/MainViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Reactant
4 | //
5 | // Created by matoushybl on 03/16/2017.
6 | // Copyright (c) 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Reactant
10 | import RxSwift
11 |
12 | final class MainViewController: ControllerBase {
13 |
14 | struct Reactions {
15 | let openTable: () -> Void
16 | }
17 |
18 | private let reactions: Reactions
19 |
20 | init(reactions: Reactions) {
21 | self.reactions = reactions
22 | // this needs to be called like this, because of a bug in Swift
23 | super.init(title: "Main")
24 |
25 | // set inital state of RootView
26 | rootView.componentState = Date()
27 | }
28 |
29 | override func update() {
30 | // do nothing since this controller has void state
31 | }
32 |
33 | // Act according to action from RootView
34 | override func act(on action: MainRootView.ActionType) {
35 | switch action {
36 | case .updateLabel:
37 | // when this action is sent from RootView, set new state to RootView's componentState
38 | rootView.componentState = Date()
39 | break
40 | // just a dummy action
41 | case .openTable:
42 | reactions.openTable()
43 | break
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Source/Core/Properties+Core.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Properties+Core.swift
3 | // Reactant
4 | //
5 | // Created by Tadeáš Kříž on 12/06/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension Properties {
12 |
13 | public static let layoutMargins = Property(defaultValue: .zero)
14 | public static let closeButtonTitle = Property(defaultValue: "Close")
15 | public static let defaultBackButton = Property()
16 | }
17 |
18 | extension Properties.Style {
19 |
20 | public static let controllerRoot = style(for: ControllerRootViewContainer.self)
21 |
22 | /// NOTE: Applied after `controllerRoot` style
23 | public static let dialogControllerRoot = style(for: ControllerRootViewContainer.self) { root in
24 | root.backgroundColor = UIColor.black.fadedOut(by: 20%)
25 | }
26 | public static let dialog = style(for: UIView.self)
27 | public static let dialogContentContainer = style(for: UIView.self)
28 | public static let scroll = style(for: UIScrollView.self)
29 | public static let button = style(for: UIButton.self)
30 | public static let control = style(for: UIControl.self)
31 | public static let container = style(for: ContainerView.self)
32 | public static let view = style(for: UIView.self)
33 | public static let textField = style(for: TextField.self)
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/Source/Utils/RangeReplaceableCollection+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RangeReplaceableCollection+Utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 21.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension RangeReplaceableCollection {
12 |
13 | // TODO: Make sure no one is using it and remove from Reactant ASAP
14 | /**
15 | * - parameter until: actually a `while` in disguise, pass a closure that needs to be true for as long as many you want elements
16 | * - WARNING: Although the name implies that this function takes elements *until* the condition is `true`,
17 | * it's quite the opposite. Tread carefully when using this function as it does the same job as `prefix(while:)`
18 | * along with `removeFirst(n:)` with the difference that `prefix(while:)` needs to be converted to array like so:
19 | * ```
20 | * let newArray = Array(prefix(while: { /* condition */ }))
21 | * oldArray.removeFirst(newArray.count)
22 | * ```
23 | */
24 | @available(*, deprecated, message: "This method will be removed in Reactant 2.0 as it doesn't provide any value to Reactant Architecture itself.")
25 | public mutating func remove(until: (Iterator.Element) -> Bool) -> [Iterator.Element] {
26 | let result = Array(prefix(while: until))
27 | removeFirst(result.count)
28 | return result
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Source/TableView/Identifier/AnyTableViewHeaderFooterIdentifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnyTableViewHeaderFooterIdentifier.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct AnyTableViewHeaderFooterIdentifier {
12 |
13 | internal let name: String
14 | internal let type: UITableViewHeaderFooterView.Type
15 | }
16 |
17 | extension TableViewHeaderFooterIdentifier {
18 |
19 | public func typeErased() -> AnyTableViewHeaderFooterIdentifier {
20 | return AnyTableViewHeaderFooterIdentifier(name: name, type: TableViewHeaderFooterWrapper.self)
21 | }
22 | }
23 |
24 | extension UITableView {
25 |
26 | public func register(identifier: AnyTableViewHeaderFooterIdentifier) {
27 | register(identifier.type, forHeaderFooterViewReuseIdentifier: identifier.name)
28 | }
29 |
30 | public func unregister(identifier: AnyTableViewHeaderFooterIdentifier) {
31 | register(nil as AnyClass?, forHeaderFooterViewReuseIdentifier: identifier.name)
32 | }
33 |
34 | public func dequeue(identifier: AnyTableViewHeaderFooterIdentifier) -> UITableViewHeaderFooterView {
35 | guard let view = dequeueReusableHeaderFooterView(withIdentifier: identifier.name) else {
36 | fatalError("\(identifier) is not registered.")
37 | }
38 | return view
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Source/Validation/Rules/Rules+String.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Rules+String.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 20.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Rules {
12 |
13 | public struct String {
14 |
15 | private static let emailPredicate = NSPredicate(format: "SELF MATCHES %@", "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}")
16 |
17 | public static let notEmpty = Rule { value in
18 | guard let value = value, value.isEmpty == false else {
19 | return .invalid
20 | }
21 |
22 | return nil
23 | }
24 |
25 | public static func minLength(_ length: Int) -> Rule {
26 | return Rule { value in
27 | guard let value = value, value.count >= length else {
28 | return .invalid
29 | }
30 | return nil
31 | }
32 | }
33 |
34 | public static let email = Rule { value in
35 | guard Rules.String.notEmpty.test(value) else {
36 | return .empty
37 | }
38 |
39 | guard emailPredicate.evaluate(with: value) else { return .invalid }
40 |
41 | return nil
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Source/TableView/Configuration+TableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Configuration+TableView.swift
3 | // Reactant
4 | //
5 | // Created by Robin Krenecky on 24/04/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public final class TableViewConfiguration: BaseSubConfiguration {
12 | public var tableView: (ReactantTableView) -> Void {
13 | get {
14 | return configuration.get(valueFor: Properties.Style.TableView.tableView)
15 | }
16 | set {
17 | configuration.set(Properties.Style.TableView.tableView, to: newValue)
18 | }
19 | }
20 |
21 | public var headerFooterWrapper: (UITableViewHeaderFooterView) -> Void {
22 | get {
23 | return configuration.get(valueFor: Properties.Style.TableView.headerFooterWrapper)
24 | }
25 | set {
26 | configuration.set(Properties.Style.TableView.headerFooterWrapper, to: newValue)
27 | }
28 | }
29 |
30 | public var cellWrapper: (UITableViewCell) -> Void {
31 | get {
32 | return configuration.get(valueFor: Properties.Style.TableView.cellWrapper)
33 | }
34 | set {
35 | configuration.set(Properties.Style.TableView.cellWrapper, to: newValue)
36 | }
37 | }
38 | }
39 |
40 | public extension Configuration.Style {
41 | var tableView: TableViewConfiguration {
42 | return TableViewConfiguration(configuration: configuration)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Source/TableView/TableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewCell.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 13.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol TableViewCell {
12 |
13 | /**
14 | * The style of selected cells.
15 | * Use `UITableViewCellSelectionStyle` constants to set the value of the `selectionStyle` property.
16 | */
17 | var selectionStyle: UITableViewCell.SelectionStyle { get }
18 |
19 | /**
20 | * The style of focused cells.
21 | * Use `UITableViewCellFocusStyle` constants to set the value of the `focusStyle` property.
22 | */
23 | @available(iOS 9.0, *)
24 | var focusStyle: UITableViewCell.FocusStyle { get }
25 |
26 | /// Called after the user lifts the finger after tapping the cell.
27 | func setSelected(_ selected: Bool, animated: Bool)
28 |
29 | /// Called when user taps the cell.
30 | func setHighlighted(_ highlighted: Bool, animated: Bool)
31 | }
32 |
33 | extension TableViewCell {
34 |
35 | public var selectionStyle: UITableViewCell.SelectionStyle {
36 | return .default
37 | }
38 |
39 | @available(iOS 9.0, *)
40 | public var focusStyle: UITableViewCell.FocusStyle {
41 | return .default
42 | }
43 |
44 | public func setSelected(_ selected: Bool, animated: Bool) {
45 | }
46 |
47 | public func setHighlighted(_ highlighted: Bool, animated: Bool) {
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/getting-started/troubleshooting.md:
--------------------------------------------------------------------------------
1 |
2 | [cocoapods]: http://cocoapods.org
3 |
4 | # Troubleshooting Tips
5 | As we all know, not always everything goes according to plan in IT. That's why we created this section with solutions to problems you are most likely to encounter.
6 |
7 | **NOTE**: Always clean the build after trying any of the provided solutions. Some things may depend on it. The shortcut in Xcode is `Cmd+Shift+K`.
8 |
9 | ## General
10 | - try `pod repo update` to update your Cocoapods repository
11 | - try `pod install` to update the Pods and reopen the `.xcworkspace` (this is important otherwise the changes might not take effect).
12 |
13 | ## Reactant
14 |
15 | ## ReactantUI
16 | **I get a compiler error saying that generated things do not exist.**
17 |
18 | Make sure that in `Build Phases`->`Compile Sources` the `GeneratedUI.swift` file is the topmost one.
19 |
20 | ---
21 |
22 | **I get an error ".../Application/Sources/Generated/GeneratedUI.swift no such file or directory".**
23 |
24 | You need to create the `Generated/` folder yourself. If you're in the root folder of your project, you can use this command: `mkdir ./Application/Generated/`.
25 |
26 | ---
27 |
28 | ## ReactantCLI
29 | **I created a new project with `reactant init` command and it doesn't run.**
30 |
31 | It's possible that something went wrong during the project creation. Make sure you have [Cocoapods][cocoapods] installed and afterwards try to call `pod repo update` and afterwards `pod install`. If it still doesn't work, consider creating a new project.
32 |
--------------------------------------------------------------------------------
/Source/Core/Component/ComponentBase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComponentBase.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 08.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | open class ComponentBase: ComponentWithDelegate {
12 |
13 | public typealias StateType = STATE
14 | public typealias ActionType = ACTION
15 |
16 | public let lifetimeDisposeBag = DisposeBag()
17 |
18 | public let componentDelegate = ComponentDelegate>()
19 |
20 | open var action: Observable {
21 | return componentDelegate.action
22 | }
23 |
24 | /**
25 | * Collection of Component's `Observable`s which are merged into `Component.action`.
26 | * - Note: When listening to Component's actions, using `action` is preferred to `actions`.
27 | */
28 | open var actions: [Observable] {
29 | return []
30 | }
31 |
32 | open func needsUpdate() -> Bool {
33 | return true
34 | }
35 |
36 | public init(canUpdate: Bool = true) {
37 | componentDelegate.ownerComponent = self
38 |
39 | resetActions()
40 |
41 | afterInit()
42 |
43 | componentDelegate.canUpdate = canUpdate
44 | }
45 |
46 | open func afterInit() {
47 | }
48 |
49 | open func update() {
50 | }
51 |
52 | public func observeState(_ when: ObservableStateEvent) -> Observable {
53 | return componentDelegate.observeState(when)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ReactantPrototyping/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UIEdgeInsets+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIEdgeInsets+Init.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIEdgeInsets {
12 |
13 | public init(left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) {
14 | self.init(top: 0, left: left, bottom: bottom, right: right)
15 | }
16 |
17 | public init(top: CGFloat, bottom: CGFloat = 0, right: CGFloat = 0) {
18 | self.init(top: top, left: 0, bottom: bottom, right: right)
19 | }
20 |
21 | public init(top: CGFloat, left: CGFloat, right: CGFloat = 0) {
22 | self.init(top: top, left: left, bottom: 0, right: right)
23 | }
24 |
25 | public init(top: CGFloat, left: CGFloat, bottom: CGFloat) {
26 | self.init(top: top, left: left, bottom: bottom, right: 0)
27 | }
28 |
29 | public init(_ all: CGFloat) {
30 | self.init(horizontal: all, vertical: all)
31 | }
32 |
33 | public init(horizontal: CGFloat, vertical: CGFloat) {
34 | self.init(top: vertical, left: horizontal, bottom: vertical, right: horizontal)
35 | }
36 |
37 | public init(horizontal: CGFloat, top: CGFloat = 0, bottom: CGFloat = 0) {
38 | self.init(top: top, left: horizontal, bottom: bottom, right: horizontal)
39 | }
40 |
41 | public init(vertical: CGFloat, left: CGFloat = 0, right: CGFloat = 0) {
42 | self.init(top: vertical, left: left, bottom: vertical, right: right)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/NSAttributedString+AttributeTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedString+AttributeTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class NSAttributedStringAttributeTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("AttributedString + AttributedString") {
17 | it("sums") {
18 | expect(NSAttributedString(string: "A") + NSAttributedString(string: "B")) == NSAttributedString(string: "AB")
19 | }
20 | }
21 | describe("String + AttributedString") {
22 | it("sums") {
23 | expect("A" + NSAttributedString(string: "B")) == NSAttributedString(string: "AB")
24 | }
25 | }
26 | describe("AttributedString + String") {
27 | it("sums") {
28 | expect(NSAttributedString(string: "A") + "B") == NSAttributedString(string: "AB")
29 | }
30 | }
31 | describe("attributed") {
32 | it("creates AttributedString") {
33 | let attributes = [Attribute.baselineOffset(1), Attribute.expansion(1)]
34 | let attributedString = NSAttributedString(string: "A", attributes: attributes.toDictionary())
35 |
36 | expect("A".attributed(attributes)) == attributedString
37 | expect("A".attributed(Attribute.baselineOffset(1), Attribute.expansion(1))) == attributedString
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/TVPrototyping/Components/ButtonTest/ButtonController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ButtonController.swift
3 | // TVPrototyping
4 | //
5 | // Created by Matous Hybl on 03/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Reactant
10 |
11 | class ButtonController: ControllerBase {
12 |
13 | override func afterInit() {
14 | tabBarItem = UITabBarItem(title: "Button", image: nil, tag: 0)
15 | }
16 | }
17 |
18 | final class ButtonRootView: ViewBase, RootView {
19 |
20 | private let button = UIButton(title: "Button")
21 |
22 | override func loadView() {
23 | children(button)
24 |
25 | button.setTitleColor(.black, for: .normal)
26 | button.setBackgroundColor(.blue, for: .normal)
27 | button.setBackgroundColor(.red, for: .focused)
28 | }
29 |
30 | override func setupConstraints() {
31 | button.snp.makeConstraints { make in
32 | make.center.equalToSuperview()
33 | make.size.equalTo(CGSize(width: 300, height: 100))
34 | }
35 | }
36 |
37 | override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
38 | coordinator.addCoordinatedAnimations({
39 | UIView.animate(withDuration: 0.3, animations: {
40 | if self.button.isFocused {
41 | self.button.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
42 | } else {
43 | self.button.transform = .identity
44 | }
45 | })
46 |
47 | }, completion: nil)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Source/Core/View/Internal/DialogView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DialogView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 09.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public final class DialogView: ViewBase {
12 |
13 | private let contentContainer = ContainerView()
14 | private let content: UIView
15 |
16 | public override var configuration: Configuration {
17 | didSet {
18 | contentContainer.configuration = configuration
19 | configuration.get(valueFor: Properties.Style.dialogContentContainer)(contentContainer)
20 | configuration.get(valueFor: Properties.Style.dialog)(self)
21 | }
22 | }
23 |
24 | public init(content: UIView) {
25 | self.content = content
26 |
27 | super.init()
28 | }
29 |
30 | override public func loadView() {
31 | children(
32 | contentContainer.children(
33 | content
34 | )
35 | )
36 | }
37 |
38 | override public func setupConstraints() {
39 | contentContainer.snp.makeConstraints { make in
40 | make.leading.greaterThanOrEqualTo(snp.leadingMargin)
41 | make.top.greaterThanOrEqualTo(snp.topMargin)
42 | make.trailing.greaterThanOrEqualTo(snp.trailingMargin)
43 | make.bottom.lessThanOrEqualTo(snp.bottomMargin)
44 | make.center.equalTo(self)
45 | }
46 |
47 | content.snp.makeConstraints { make in
48 | make.edges.equalTo(contentContainer)
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Source/CollectionView/ReactantCollectionView+Delegates.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReactantCollectionView+Delegates.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 13.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension ReactantCollectionView {
12 |
13 | #if os(iOS)
14 | /**
15 | * The tint color for the refresh control.
16 | * The default value of this property is nil.
17 | */
18 | public var refreshControlTintColor: UIColor? {
19 | get {
20 | return refreshControl?.tintColor
21 | }
22 | set {
23 | refreshControl?.tintColor = newValue
24 | }
25 | }
26 | #endif
27 |
28 | /**
29 | * The basic appearance of the activity indicator.
30 | * See **UIActivityIndicatorViewStyle** for the available styles. The default value is white.
31 | */
32 | public var activityIndicatorStyle: UIActivityIndicatorView.Style {
33 | get {
34 | return loadingIndicator.style
35 | }
36 | set {
37 | loadingIndicator.style = newValue
38 | }
39 | }
40 |
41 | /**
42 | * The distance that the content view is inset from the enclosing scroll view.
43 | * Use this property to add to the scrolling area around the content. The unit of size is points. The default value is `UIEdgeInsetsZero`.
44 | */
45 | public var contentInset: UIEdgeInsets {
46 | get {
47 | return collectionView.contentInset
48 | }
49 | set {
50 | collectionView.contentInset = newValue
51 | }
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/CGRect+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGRect+Init.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension CGRect {
12 |
13 | public init(x: CGFloat, width: CGFloat = 0, height: CGFloat = 0) {
14 | self.init(x: x, y: 0, width: width, height: height)
15 | }
16 |
17 | public init(y: CGFloat, width: CGFloat = 0, height: CGFloat = 0) {
18 | self.init(x: 0, y: y, width: width, height: height)
19 | }
20 |
21 | public init(x: CGFloat, y: CGFloat) {
22 | self.init(x: x, y: y, width: 0, height: 0)
23 | }
24 |
25 | public init(x: CGFloat, y: CGFloat, width: CGFloat) {
26 | self.init(x: x, y: y, width: width, height: 0)
27 | }
28 |
29 | public init(x: CGFloat, y: CGFloat, height: CGFloat) {
30 | self.init(x: x, y: y, width: 0, height: height)
31 | }
32 |
33 | public init(width: CGFloat) {
34 | self.init(x: 0, y: 0, width: width, height: 0)
35 | }
36 |
37 | public init(height: CGFloat) {
38 | self.init(x: 0, y: 0, width: 0, height: height)
39 | }
40 |
41 | public init(width: CGFloat, height: CGFloat) {
42 | self.init(x: 0, y: 0, width: width, height: height)
43 | }
44 |
45 | public init(x: CGFloat = 0, y: CGFloat = 0, size: CGSize) {
46 | self.init(origin: CGPoint(x: x, y: y), size: size)
47 | }
48 |
49 | public init(origin: CGPoint, width: CGFloat = 0, height: CGFloat = 0) {
50 | self.init(origin: origin, size: CGSize(width: width, height: height))
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Source/Utils/RxUtils/UIBarButtonItem+ClosureAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIBarButtonItem+ClosureAction.swift
3 | // Reactant
4 | //
5 | // Created by Tadeas Kriz on 06/10/2016.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 | import RxCocoa
12 |
13 | /// Extension of UIBarButtonItem, that adds option to use closure instead of target and selector
14 | extension UIBarButtonItem {
15 |
16 | public convenience init(image: UIImage?, style: UIBarButtonItem.Style, action: (() -> Void)? = nil) {
17 | self.init(image: image, style: style, target: nil, action: nil)
18 |
19 | register(action: action)
20 | }
21 |
22 | public convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItem.Style,
23 | action: (() -> Void)? = nil) {
24 | self.init(image: image, landscapeImagePhone: landscapeImagePhone, style: style, target: nil, action: nil)
25 |
26 | register(action: action)
27 | }
28 |
29 | public convenience init(title: String?, style: UIBarButtonItem.Style, action: (() -> Void)? = nil) {
30 | self.init(title: title, style: style, target: nil, action: nil)
31 |
32 | register(action: action)
33 | }
34 |
35 | public convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, action: (() -> Void)? = nil) {
36 | self.init(barButtonSystemItem: systemItem, target: nil, action: nil)
37 |
38 | register(action: action)
39 | }
40 |
41 | private func register(action: (() -> Void)?) {
42 | if let action = action {
43 | _ = rx.tap.takeUntil(rx.deallocating).subscribe(onNext: action)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/CGAffineTransform+ShortcutTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGAffineTransform+ShortcutTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class CGAffineTransformShortcutTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("rotate") {
17 | it("creates CGAffineTransform") {
18 | expect(rotate()) == CGAffineTransform(rotationAngle: 0)
19 | expect(rotate(10)) == CGAffineTransform(rotationAngle: 10)
20 | }
21 | }
22 | describe("translate") {
23 | it("creates CGAffineTransform") {
24 | expect(translate()) == CGAffineTransform(translationX: 0, y: 0)
25 | expect(translate(x: 1)) == CGAffineTransform(translationX: 1, y: 0)
26 | expect(translate(y: 1)) == CGAffineTransform(translationX: 0, y: 1)
27 | expect(translate(x: 1, y: 1)) == CGAffineTransform(translationX: 1, y: 1)
28 | }
29 | }
30 | describe("scale") {
31 | it("creates CGAffineTransform") {
32 | expect(scale()) == CGAffineTransform(scaleX: 1, y: 1)
33 | expect(scale(x: 2)) == CGAffineTransform(scaleX: 2, y: 1)
34 | expect(scale(y: 2)) == CGAffineTransform(scaleX: 1, y: 2)
35 | expect(scale(x: 2, y: 2)) == CGAffineTransform(scaleX: 2, y: 2)
36 | }
37 | }
38 | describe("+") {
39 | it("sums vectors") {
40 | expect(translate(x: 5) + translate(y: 3)) == translate(x: 5, y: 3)
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Source/TableView/Identifier/AnyTableViewCellIdentifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnyTableViewCellIdentifier.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct AnyTableViewCellIdentifier {
12 |
13 | internal let name: String
14 | internal let type: UITableViewCell.Type
15 | }
16 |
17 | extension TableViewCellIdentifier {
18 |
19 | public func typeErased() -> AnyTableViewCellIdentifier {
20 | return AnyTableViewCellIdentifier(name: name, type: TableViewCellWrapper.self)
21 | }
22 | }
23 |
24 | extension UITableView {
25 |
26 | public func register(identifier: AnyTableViewCellIdentifier) {
27 | register(identifier.type, forCellReuseIdentifier: identifier.name)
28 | }
29 |
30 | public func unregister(identifier: AnyTableViewCellIdentifier) {
31 | register(nil as AnyClass?, forCellReuseIdentifier: identifier.name)
32 | }
33 |
34 | public func dequeue(identifier: AnyTableViewCellIdentifier) -> UITableViewCell {
35 | guard let cell = dequeueReusableCell(withIdentifier: identifier.name) else {
36 | fatalError("\(identifier) is not registered.")
37 | }
38 | return cell
39 | }
40 |
41 | public func dequeue(identifier: AnyTableViewCellIdentifier, for indexPath: IndexPath) -> UITableViewCell {
42 | return dequeueReusableCell(withIdentifier: identifier.name, for: indexPath)
43 | }
44 |
45 | public func dequeue(identifier: AnyTableViewCellIdentifier, forRow row: Int, inSection section: Int = 0) -> UITableViewCell {
46 | return dequeue(identifier: identifier, for: IndexPath(row: row, section: section))
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/UIButton+UtilsTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIButton+UtilsTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class UIButtonUtilsTest: QuickSpec {
14 |
15 | override func spec() {
16 | describe("UIButton") {
17 | describe("init") {
18 | it("creates UIButton with title") {
19 | expect(UIButton(title: "title").title(for: UIControl.State())) == "title"
20 | }
21 | }
22 | describe("setBackgroundColor") {
23 | it("sets background color") {
24 | let button = UIButton()
25 | let controlState = UIControl.State.highlighted
26 |
27 | button.setBackgroundColor(UIColor.green, for: controlState)
28 |
29 | let image = button.backgroundImage(for: controlState)
30 | if let pixelData = image?.cgImage?.dataProvider?.data, let data = CFDataGetBytePtr(pixelData) {
31 | let r = CGFloat(data[0]) / 255
32 | let g = CGFloat(data[1]) / 255
33 | let b = CGFloat(data[2]) / 255
34 | let a = CGFloat(data[3]) / 255
35 |
36 | expect(UIColor(red: r, green: g, blue: b, alpha: a)) == UIColor.green
37 | expect(image?.size) == CGSize(1)
38 | } else {
39 | fail("Cannot find color of background image.")
40 | }
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Source/Core/Controller/DialogControllerBase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DialogControllerBase.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 08.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class DialogControllerBase: ControllerBase where ROOT: Component {
12 |
13 | private let rootViewContainer = ControllerRootViewContainer()
14 | public var dialogView: DialogView
15 |
16 | open override var configuration: Configuration {
17 | didSet {
18 | dialogView.configuration = configuration
19 | configuration.get(valueFor: Properties.Style.dialogControllerRoot)(rootViewContainer)
20 | }
21 | }
22 |
23 | public override init(title: String = "", root: ROOT = ROOT()) {
24 | dialogView = DialogView(content: root)
25 |
26 | super.init(title: title, root: root)
27 |
28 | modalTransitionStyle = .crossDissolve
29 | modalPresentationStyle = .overCurrentContext
30 | }
31 |
32 | open override func loadView() {
33 | view = rootViewContainer
34 |
35 | view.addSubview(dialogView)
36 | }
37 |
38 | open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
39 | let dismissalListener = presentingViewController as? DialogDismissalListener
40 | dismissalListener?.dialogWillDismiss()
41 | super.dismiss(animated: flag) {
42 | dismissalListener?.dialogDidDismiss()
43 | completion?()
44 | }
45 | }
46 |
47 | open override func updateRootViewConstraints() {
48 | dialogView.snp.updateConstraints { make in
49 | make.edges.equalTo(view)
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Source/Core/View/ContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContainerView.swift
3 | // Reactant
4 | //
5 | // Created by Tadeáš Kříž on 05/04/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class ContainerView: UIView, Configurable {
12 |
13 | open var configuration: Configuration = .global {
14 | didSet {
15 | layoutMargins = configuration.get(valueFor: Properties.layoutMargins)
16 | configuration.get(valueFor: Properties.Style.container)(self)
17 | }
18 | }
19 |
20 | open override class var requiresConstraintBasedLayout: Bool {
21 | return true
22 | }
23 |
24 | #if ENABLE_SAFEAREAINSETS_FALLBACK
25 | open override var frame: CGRect {
26 | didSet {
27 | fallback_computeSafeAreaInsets()
28 | }
29 | }
30 |
31 | open override var bounds: CGRect {
32 | didSet {
33 | fallback_computeSafeAreaInsets()
34 | }
35 | }
36 |
37 | open override var center: CGPoint {
38 | didSet {
39 | fallback_computeSafeAreaInsets()
40 | }
41 | }
42 | #endif
43 |
44 | public required init?(coder aDecoder: NSCoder) {
45 | super.init(coder: aDecoder)
46 |
47 | reloadConfiguration()
48 | }
49 |
50 | public override init(frame: CGRect) {
51 | super.init(frame: frame)
52 |
53 | reloadConfiguration()
54 | }
55 |
56 | public convenience init() {
57 | self.init(frame: CGRect.zero)
58 |
59 | reloadConfiguration()
60 | }
61 |
62 | open override func addSubview(_ view: UIView) {
63 | view.translatesAutoresizingMaskIntoConstraints = false
64 |
65 | super.addSubview(view)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Source/Core/Controller/ScrollControllerBase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollControllerBase.swift
3 | // Reactant
4 | //
5 | // Created by Tadeáš Kříž on 09/09/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class ScrollControllerBase: ControllerBase where ROOT: Component {
12 |
13 | public let scrollView = UIScrollView()
14 |
15 | open override var configuration: Configuration {
16 | didSet {
17 | configuration.get(valueFor: Properties.Style.scroll)(scrollView)
18 | }
19 | }
20 |
21 | open override func loadView() {
22 | view = ControllerRootViewContainer().with(configuration: configuration)
23 |
24 | view.children(
25 | scrollView.children(
26 | rootView
27 | )
28 | )
29 | }
30 |
31 | public override init(title: String = "", root: ROOT = ROOT()) {
32 | super.init(title: title, root: root)
33 | }
34 |
35 | open override func updateRootViewConstraints() {
36 | scrollView.snp.updateConstraints { make in
37 | make.edges.equalTo(view)
38 | }
39 |
40 | rootView.snp.updateConstraints { make in
41 | make.leading.equalTo(view)
42 | make.top.equalTo(scrollView)
43 | make.trailing.equalTo(view)
44 | make.bottom.equalTo(scrollView)
45 | }
46 | }
47 |
48 | open override func viewDidLayoutSubviews() {
49 | scrollView.contentSize = rootView.bounds.size
50 |
51 | super.viewDidLayoutSubviews()
52 | }
53 | }
54 |
55 | extension ScrollControllerBase: Scrollable {
56 |
57 | public func scrollToTop(animated: Bool) {
58 | scrollView.scrollToTop(animated: animated)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Tests/TestUtils/Rx+recording.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Rx+recording.swift
3 | // ReactantTests
4 | //
5 | // Created by Matyáš Kříž on 17/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 | import RxTest
11 | import Reactant
12 |
13 | private final class RecordingObserver
14 | : ObserverType {
15 | typealias Element = ElementType
16 |
17 | /// Recorded events.
18 | fileprivate(set) var events = [Event]()
19 |
20 | init() { }
21 |
22 | /// Notify observer about sequence event.
23 | ///
24 | /// - parameter event: Event that occurred.
25 | public func on(_ event: Event) {
26 | events.append(event)
27 | }
28 | }
29 |
30 | struct Recording {
31 | let events: [Event]
32 | let elements: [Element]
33 | let didComplete: Bool
34 | let didError: Bool
35 | let error: Swift.Error?
36 |
37 | init(events: [Event]) {
38 | self.events = events
39 | elements = events.compactMap { $0.element }
40 |
41 | if case .completed? = events.last {
42 | didComplete = true
43 | } else {
44 | didComplete = false
45 | }
46 |
47 | if case .error(let error)? = events.last {
48 | self.error = error
49 | } else {
50 | self.error = nil
51 | }
52 | didError = error != nil
53 | }
54 | }
55 |
56 | func recording(of recordedObservable: Observable, do work: () throws -> Void) rethrows -> Recording {
57 | let recordingObserver = RecordingObserver()
58 | let disposable = recordedObservable.subscribe(recordingObserver)
59 | defer { disposable.dispose() }
60 | try work()
61 | return Recording(events: recordingObserver.events)
62 | }
63 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Reactant (1.0.0):
3 | - Reactant/Core (= 1.0.0)
4 | - Reactant/Result (= 1.0.0)
5 | - Reactant/CollectionView (1.0.0):
6 | - Reactant/Core
7 | - RxCocoa (~> 3.0)
8 | - RxDataSources (~> 1.0)
9 | - Reactant/Configuration (1.0.0):
10 | - RxSwift (~> 3.0)
11 | - SnapKit (~> 3.0)
12 | - Reactant/Core (1.0.0):
13 | - Reactant/Configuration
14 | - RxCocoa (~> 3.0)
15 | - RxOptional (~> 3.0)
16 | - RxSwift (~> 3.0)
17 | - SnapKit (~> 3.0)
18 | - Reactant/Result (1.0.0):
19 | - Result (~> 3.0)
20 | - RxOptional (~> 3.0)
21 | - RxSwift (~> 3.0)
22 | - Reactant/TableView (1.0.0):
23 | - Reactant/Core
24 | - RxCocoa (~> 3.0)
25 | - RxDataSources (~> 1.0)
26 | - Reactant/Validation (1.0.0):
27 | - Result (~> 3.0)
28 | - Result (3.0.0)
29 | - RxCocoa (3.0.1):
30 | - RxSwift (~> 3.0)
31 | - RxDataSources (1.0.3):
32 | - RxCocoa (~> 3.0)
33 | - RxSwift (~> 3.0)
34 | - RxOptional (3.1.3):
35 | - RxCocoa
36 | - RxSwift
37 | - RxSwift (3.0.1)
38 | - SnapKit (3.2.0)
39 |
40 | DEPENDENCIES:
41 | - Reactant (from `../`)
42 | - Reactant/CollectionView (from `../`)
43 | - Reactant/TableView (from `../`)
44 | - Reactant/Validation (from `../`)
45 |
46 | EXTERNAL SOURCES:
47 | Reactant:
48 | :path: "../"
49 |
50 | SPEC CHECKSUMS:
51 | Reactant: 4062d70c2ad431246e45e4cde2d3ac265bb7252f
52 | Result: 1b3e431f37cbcd3ad89c6aa9ab0ae55515fae3b6
53 | RxCocoa: 15a52fc590dcc700cb4a690a633b5c5184ce3a78
54 | RxDataSources: a021a0e944ba5f7991259829d973afdbfa719c0b
55 | RxOptional: b97b59183af80d1e729ac9e51d09a10be668d6a8
56 | RxSwift: af5680055c4ad04480189c52d28385b1029493a6
57 | SnapKit: 1ca44df72cfa543218d177cb8aab029d10d86ea7
58 |
59 | PODFILE CHECKSUM: 8b14c208eae1cb2624c4f57db51a67aca61a132f
60 |
61 | COCOAPODS: 1.2.1
62 |
--------------------------------------------------------------------------------
/ReactantPrototyping/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Example/Application/Sources/Wireframe/MainWireframe.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainWireframe.swift
3 | // Reactant
4 | //
5 | // Created by Matous Hybl on 3/24/17.
6 | // Copyright © 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Reactant
10 |
11 | class MainWireframe: Wireframe {
12 |
13 | private let dependencyModule: DependencyModule
14 |
15 | init(dependencyModule: DependencyModule) {
16 | self.dependencyModule = dependencyModule
17 | }
18 |
19 | func entrypoint() -> UIViewController {
20 | return UINavigationController(rootViewController: mainController())
21 | }
22 |
23 | private func mainController() -> UIViewController {
24 | return create { provider in
25 | let reactions = MainViewController.Reactions(openTable: {
26 | provider.navigation?.push(controller: self.tableViewController())
27 | })
28 |
29 | return MainViewController(reactions: reactions)
30 | }
31 | }
32 |
33 | private func tableViewController() -> UIViewController {
34 | return create { provider in
35 | let dependencies = TableViewController.Dependencies(nameService: dependencyModule.nameService)
36 | let reactions = TableViewController.Reactions(displayName: { name in
37 | provider.navigation?.present(controller: self.nameAlertController(name: name))
38 | })
39 | return TableViewController(dependencies: dependencies, reactions: reactions)
40 | }
41 | }
42 |
43 | private func nameAlertController(name: String) -> UIViewController {
44 | let controller = UIAlertController(title: "This is a name", message: name, preferredStyle: .actionSheet)
45 | controller.addAction(UIAlertAction(title: "Close", style: .cancel, handler: { _ in controller.dismiss() }))
46 | return controller
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Source/CollectionView/Identifier/CollectionSupplementaryViewIdentifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionSupplementaryViewIdentifier.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct CollectionSupplementaryViewIdentifier {
12 |
13 | internal let name: String
14 | internal let kind: String
15 |
16 | public init(name: String = NSStringFromClass(T.self), kind: String) {
17 | self.name = name
18 | self.kind = kind
19 | }
20 | }
21 |
22 | extension UICollectionView {
23 |
24 | public func register(identifier: CollectionSupplementaryViewIdentifier) {
25 | register(CollectionReusableViewWrapper.self, forSupplementaryViewOfKind: identifier.kind, withReuseIdentifier: identifier.name)
26 | }
27 |
28 | public func unregister(identifier: CollectionSupplementaryViewIdentifier) {
29 | register(nil as AnyClass?, forSupplementaryViewOfKind: identifier.kind, withReuseIdentifier: identifier.name)
30 | }
31 | }
32 |
33 | extension UICollectionView {
34 |
35 | public func dequeue(identifier: CollectionSupplementaryViewIdentifier, for indexPath: IndexPath) -> CollectionReusableViewWrapper {
36 | guard let view = dequeueReusableSupplementaryView(ofKind: identifier.kind, withReuseIdentifier: identifier.name, for: indexPath) as? CollectionReusableViewWrapper else {
37 | fatalError("\(identifier) is not registered.")
38 | }
39 | return view
40 | }
41 |
42 | public func dequeue(identifier: CollectionSupplementaryViewIdentifier, forRow row: Int, inSection section: Int = 0) -> CollectionReusableViewWrapper {
43 | return dequeue(identifier: identifier, for: IndexPath(row: row, section: section))
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Source/Utils/Internal/InternalUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InternalUtils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 26.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | internal func degreesToRadians(_ value: Double) -> Double {
12 | return value * Double.pi / 180.0
13 | }
14 |
15 | internal func radiansToDegrees(_ value: Double) -> Double {
16 | return value * 180.0 / Double.pi
17 | }
18 |
19 | extension Double {
20 |
21 | internal func equal(to value: Double, precision: Double = Double.ulpOfOne) -> Bool {
22 | return abs(self - value) <= precision
23 | }
24 | }
25 |
26 | #if DEBUG
27 | func fatalError(_ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) -> Never {
28 | #if _runtime(_ObjC)
29 | NSException(name: .internalInconsistencyException, reason: message(), userInfo: nil).raise()
30 | #endif
31 |
32 | Swift.fatalError(message(), file: file, line: line)
33 | }
34 |
35 | func preconditionFailure(_ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) -> Never {
36 | #if _runtime(_ObjC)
37 | NSException(name: .internalInconsistencyException, reason: message(), userInfo: nil).raise()
38 | #endif
39 |
40 | Swift.preconditionFailure(message(), file: file, line: line)
41 | }
42 |
43 | func precondition(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
44 | guard !condition() else { return }
45 | #if _runtime(_ObjC)
46 | NSException(name: .internalInconsistencyException, reason: message(), userInfo: nil).raise()
47 | #endif
48 |
49 | Swift.preconditionFailure(message(), file: file, line: line)
50 | }
51 | #endif
52 |
--------------------------------------------------------------------------------
/Source/CollectionView/Configuration+CollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Configuration+CollectionView.swift
3 | // Reactant
4 | //
5 | // Created by Robin Krenecky on 24/04/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public final class CollectionViewConfiguration: BaseSubConfiguration {
12 | public var collectionView: (ReactantCollectionView) -> Void {
13 | get {
14 | return configuration.get(valueFor: Properties.Style.CollectionView.collectionView)
15 | }
16 | set {
17 | configuration.set(Properties.Style.CollectionView.collectionView, to: newValue)
18 | }
19 | }
20 |
21 | public var reusableViewWrapper: (UICollectionReusableView) -> Void {
22 | get {
23 | return configuration.get(valueFor: Properties.Style.CollectionView.reusableViewWrapper)
24 | }
25 | set {
26 | configuration.set(Properties.Style.CollectionView.reusableViewWrapper, to: newValue)
27 | }
28 | }
29 |
30 | public var cellWrapper: (UICollectionViewCell) -> Void {
31 | get {
32 | return configuration.get(valueFor: Properties.Style.CollectionView.cellWrapper)
33 | }
34 | set {
35 | configuration.set(Properties.Style.CollectionView.cellWrapper, to: newValue)
36 | }
37 | }
38 |
39 | public var pageControl: (UIPageControl) -> Void {
40 | get {
41 | return configuration.get(valueFor: Properties.Style.CollectionView.pageControl)
42 | }
43 | set {
44 | configuration.set(Properties.Style.CollectionView.pageControl, to: newValue)
45 | }
46 | }
47 | }
48 |
49 | public extension Configuration.Style {
50 | var collectionView: CollectionViewConfiguration {
51 | return CollectionViewConfiguration(configuration: configuration)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Tests/Utils/Array+UtilsTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+UtilsTest.swift
3 | // ReactantTests
4 | //
5 | // Created by Matyáš Kříž on 17/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class ArrayExtensionsTest: QuickSpec {
14 | override func spec() {
15 | describe("Array extension methods") {
16 | let emptyArray = [] as [String]
17 | let nonEmptyArray = [1, 2, 3, 4]
18 | describe("arrayByAppending using empty array returns original array") {
19 | expect(emptyArray.arrayByAppending()).to(equal(emptyArray))
20 | expect(emptyArray.arrayByAppending([])).to(equal(emptyArray))
21 | expect(nonEmptyArray.arrayByAppending()).to(equal(nonEmptyArray))
22 | expect(nonEmptyArray.arrayByAppending([])).to(equal(nonEmptyArray))
23 | }
24 | describe("arrayByAppending can append one element") {
25 | expect(emptyArray.arrayByAppending("testko")).to(equal(["testko"]))
26 | expect(emptyArray.arrayByAppending(["testko"])).to(equal(["testko"]))
27 | expect(nonEmptyArray.arrayByAppending(5)).to(equal([1, 2, 3, 4, 5]))
28 | expect(nonEmptyArray.arrayByAppending([5])).to(equal([1, 2, 3, 4, 5]))
29 | }
30 | describe("arrayByAppending can append arbitrary number of elements") {
31 | expect(emptyArray.arrayByAppending("testko", "otestovane")).to(equal(["testko", "otestovane"]))
32 | expect(emptyArray.arrayByAppending(["testko", "otestovane"])).to(equal(["testko", "otestovane"]))
33 | expect(nonEmptyArray.arrayByAppending(5, 6, 7)).to(equal([1, 2, 3, 4, 5, 6, 7]))
34 | expect(nonEmptyArray.arrayByAppending([5, 6, 7])).to(equal([1, 2, 3, 4, 5, 6, 7]))
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/docs/parts/rootview.md:
--------------------------------------------------------------------------------
1 | # RootView
2 |
3 | As forementioned in the [Reactant architecture guide](../getting-started/architecture.md), `RootView` is a protocol and a marker for views that should be the root of a screen (filling the whole screen). Apart from being a marker, it's also used for communication with its parent controller.
4 |
5 | ## edgesForExtendedLayout
6 |
7 | This property is very similar to `UIViewController#edgesForExtendedLayout`. It specifies whether the view should be positioned behind translucent bars like the `UINavigationBar` and `UITabBar`. If you set `.all` (or `.top` / `.bottom`), the parent controller will make sure the view is under those bars. This is often used for root views that contain scroll views or tables.
8 |
9 | The default value is `[]` (or none), meaning that the root view will not be extended under those bars and thus no part of the view will be obstructed.
10 |
11 | ## viewWillAppear()
12 | Called by the parent Controller when the screen will appear.
13 |
14 | For more information take a look at [`UIViewController#viewWillAppear`](https://developer.apple.com/reference/uikit/uiviewcontroller/1621510-viewwillappear)
15 |
16 | ## viewDidAppear()
17 | Called by the parent Controller when the screen did appear.
18 |
19 | For more information take a look at [`UIViewController#viewDidAppear`](https://developer.apple.com/reference/uikit/uiviewcontroller/1621423-viewdidappear)
20 |
21 | ## viewWillDisappear()
22 | Called by the parent Controller when the screen will disappear.
23 |
24 | For more information take a look at [`UIViewController#viewWillDisappear`](https://developer.apple.com/reference/uikit/uiviewcontroller/1621485-viewwilldisappear)
25 |
26 | ## viewDidDisappear()
27 | Called by the parent Controller when the screen did disappear.
28 |
29 | For more information take a look at [`UIViewController#viewDidDisappear`](https://developer.apple.com/reference/uikit/uiviewcontroller/1621477-viewdiddisappear)
30 |
--------------------------------------------------------------------------------
/Source/CollectionView/Internal/CollectionReusableViewWrapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionReusableViewWrapper.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 14.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 |
12 | public final class CollectionReusableViewWrapper: UICollectionReusableView, Configurable {
13 |
14 | public var configurationChangeTime: clock_t = 0
15 |
16 | private var wrappedView: VIEW?
17 |
18 | public var configuration: Configuration = .global {
19 | didSet {
20 | (wrappedView as? Configurable)?.configuration = configuration
21 | configuration.get(valueFor: Properties.Style.CollectionView.reusableViewWrapper)(self)
22 | }
23 | }
24 |
25 | public var configureDisposeBag = DisposeBag()
26 |
27 | public override class var requiresConstraintBasedLayout: Bool {
28 | return true
29 | }
30 |
31 | public override func updateConstraints() {
32 | super.updateConstraints()
33 |
34 | wrappedView?.snp.updateConstraints { make in
35 | make.edges.equalTo(self)
36 | }
37 | }
38 |
39 | public func cachedViewOrCreated(factory: () -> VIEW) -> VIEW {
40 | if let wrappedView = wrappedView {
41 | return wrappedView
42 | } else {
43 | let wrappedView = factory()
44 | (wrappedView as? Configurable)?.configuration = configuration
45 | self.wrappedView = wrappedView
46 | children(wrappedView)
47 | setNeedsUpdateConstraints()
48 | return wrappedView
49 | }
50 | }
51 |
52 | public func deleteCachedView() -> VIEW? {
53 | let wrappedView = self.wrappedView
54 | wrappedView?.removeFromSuperview()
55 | self.wrappedView = nil
56 | return wrappedView
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Source/TableView/Internal/TableViewHeaderFooterWrapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewHeaderFooterWrapper.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 13.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 |
12 | public final class TableViewHeaderFooterWrapper: UITableViewHeaderFooterView, Configurable {
13 |
14 | public var configurationChangeTime: clock_t = 0
15 |
16 | private var wrappedView: VIEW?
17 |
18 | public var configuration: Configuration = .global {
19 | didSet {
20 | (wrappedView as? Configurable)?.configuration = configuration
21 | configuration.get(valueFor: Properties.Style.TableView.headerFooterWrapper)(self)
22 | }
23 | }
24 |
25 | public var configureDisposeBag = DisposeBag()
26 |
27 | public override class var requiresConstraintBasedLayout: Bool {
28 | return true
29 | }
30 |
31 | public override func updateConstraints() {
32 | super.updateConstraints()
33 |
34 | wrappedView?.snp.updateConstraints { make in
35 | make.edges.equalTo(contentView)
36 | }
37 | }
38 |
39 | public func cachedViewOrCreated(factory: () -> VIEW) -> VIEW {
40 | if let wrappedView = wrappedView {
41 | return wrappedView
42 | } else {
43 | let wrappedView = factory()
44 | (wrappedView as? Configurable)?.configuration = configuration
45 | self.wrappedView = wrappedView
46 | contentView.children(wrappedView)
47 | setNeedsUpdateConstraints()
48 | return wrappedView
49 | }
50 | }
51 |
52 | public func deleteCachedView() -> VIEW? {
53 | let wrappedView = self.wrappedView
54 | wrappedView?.removeFromSuperview()
55 | self.wrappedView = nil
56 | return wrappedView
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Source/Core/Styling/AttributedString/NSAttributedString+Attribute.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedString+Attribute.swift
3 | // Reactant
4 | //
5 | // Created by Tadeas Kriz on 5/2/17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public func + (lhs: NSAttributedString, rhs: NSAttributedString) -> NSAttributedString {
12 | let mutableString = NSMutableAttributedString(attributedString: lhs)
13 | mutableString.append(rhs)
14 | return mutableString
15 | }
16 |
17 | public func + (lhs: String, rhs: NSAttributedString) -> NSAttributedString {
18 | return lhs.attributed() + rhs
19 | }
20 |
21 | public func + (lhs: NSAttributedString, rhs: String) -> NSAttributedString {
22 | return lhs + rhs.attributed()
23 | }
24 |
25 | extension String {
26 |
27 | /**
28 | * Allows you to easily create an `NSAttributedString` out of regular `String`
29 | * For available attributes see `Attribute`.
30 | * parameter attributes: passed attributes with which NSAttributedString is created
31 | * ## Example
32 | * ```
33 | * let attributedString = "Beautiful String".attributed(.kern(1.2), .strokeWidth(1), .strokeColor(.red))
34 | * ```
35 | */
36 | public func attributed(_ attributes: [Attribute]) -> NSAttributedString {
37 | return NSAttributedString(string: self, attributes: attributes.toDictionary())
38 | }
39 |
40 | /**
41 | * Allows you to easily create an `NSAttributedString` out of regular `String`
42 | * For available attributes see `Attribute`.
43 | * parameter attributes: passed attributes with which NSAttributedString is created
44 | * ## Example
45 | * ```
46 | * let attributedString = "Beautiful String".attributed(.kern(1.2), .strokeWidth(1), .strokeColor(.red))
47 | * ```
48 | */
49 | public func attributed(_ attributes: Attribute...) -> NSAttributedString {
50 | return attributed(attributes)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Source/CollectionView/Identifier/CollectionViewCellIdentifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionViewCellIdentifier.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | public struct CollectionViewCellIdentifier {
12 |
13 | internal let name: String
14 |
15 | public init(name: String = NSStringFromClass(T.self)) {
16 | self.name = name
17 | }
18 | }
19 |
20 | extension UICollectionView {
21 |
22 | public func register(identifier: CollectionViewCellIdentifier) {
23 | register(CollectionViewCellWrapper.self, forCellWithReuseIdentifier: identifier.name)
24 | }
25 |
26 | public func unregister(identifier: CollectionViewCellIdentifier) {
27 | register(nil as AnyClass?, forCellWithReuseIdentifier: identifier.name)
28 | }
29 | }
30 |
31 | extension UICollectionView {
32 |
33 | public func dequeue(identifier: CollectionViewCellIdentifier, for indexPath: IndexPath) -> CollectionViewCellWrapper {
34 | guard let cell = dequeueReusableCell(withReuseIdentifier: identifier.name, for: indexPath) as? CollectionViewCellWrapper else {
35 | fatalError("\(identifier) is not registered.")
36 | }
37 | return cell
38 | }
39 |
40 | public func dequeue(identifier: CollectionViewCellIdentifier, forRow row: Int, inSection section: Int = 0) -> CollectionViewCellWrapper {
41 | return dequeue(identifier: identifier, for: IndexPath(row: row, section: section))
42 | }
43 |
44 | public func items(with identifier: CollectionViewCellIdentifier) ->
45 | (_ source: O) -> (_ configureCell: @escaping (Int, S.Iterator.Element, CollectionViewCellWrapper) -> Void) -> Disposable where O.Element == S {
46 | return rx.items(cellIdentifier: identifier.name)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/docs/parts/wireframe.md:
--------------------------------------------------------------------------------
1 | # Wireframe
2 |
3 | In Reactant, Wireframe is meant to handle transitions between controllers, so that no controller knows about any other controllers. To learn more, head over to our [Reactant Architecture Guide](../getting-started/architecture.md).
4 |
5 | To make this a little easier we include a protocol `Wireframe` with two helper methods:
6 |
7 | ## `create(factory:)`
8 |
9 | Use create to get access to enclosing `UINavigationController` and the created controller in reaction closures you provide the controller. This lets you break the dependency cycle.
10 |
11 | The `create` method has following signature:
12 |
13 | ```swift
14 | public func create(factory: (FutureControllerProvider) -> T) -> T
15 | ```
16 |
17 | And you would use it like so:
18 |
19 | ```swift
20 | class MainWireframe: Wireframe {
21 | func controller1() -> Controller1 {
22 | return create { provider in
23 | let reactions = Controller1.Reactions(
24 | openController2: {
25 | provider.navigation?.push(self.controller2())
26 | }
27 | )
28 | return Controller1(reactions: reactions)
29 | }
30 | }
31 |
32 | func controller2() -> Controller2 {
33 | return create { _ in
34 | return Controller1()
35 | }
36 | }
37 | }
38 | ```
39 |
40 |
41 | ## `branchNavigation(controller:)`
42 |
43 | Navigation branching is especially useful when you need to present a controller modally and want to display a navigation bar (with the possibility to dismiss the controller). When that happens, you can use the `branchNavigation` to wrap your controller inside `UINavigationController`. It will also set `leftBarButtonItem` for you that will dismiss the modal controller.
44 |
45 | ```swift
46 | public func branchNavigation(controller: UIViewController, closeButtonTitle: String?) -> UINavigationController
47 |
48 | public func branchNavigation(controller: ControllerBase) -> UINavigationController
49 | ```
50 |
--------------------------------------------------------------------------------
/Tests/Configuration/ConfigurableTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurableTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 26.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | private typealias Configuration = Reactant.Configuration
14 |
15 | class ConfigurableTest: QuickSpec {
16 |
17 | override func spec() {
18 | describe("Configurable") {
19 | let configuration = Configuration()
20 |
21 | describe("reloadConfiguration") {
22 | it("sets configuration with the same value") {
23 | let configurable = ConfigurableStub(configuration: configuration)
24 | var called = false
25 | configurable.didSetCallback = {
26 | expect(configurable.configuration) === configuration
27 | called = true
28 | }
29 |
30 | configurable.reloadConfiguration()
31 |
32 | expect(called).to(beTrue())
33 | }
34 | }
35 | describe("with") {
36 | it("sets configuration") {
37 | let differentConfiguration = Configuration()
38 | let configurable = ConfigurableStub(configuration: configuration)
39 |
40 | expect(configurable.with(configuration: differentConfiguration).configuration) === differentConfiguration
41 | }
42 | }
43 | }
44 | }
45 |
46 | private class ConfigurableStub: Configurable {
47 |
48 | var configuration: Configuration {
49 | didSet {
50 | didSetCallback()
51 | }
52 | }
53 |
54 | var didSetCallback: () -> Void = {}
55 |
56 | init(configuration: Configuration) {
57 | self.configuration = configuration
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/docs/parts/tableview.md:
--------------------------------------------------------------------------------
1 | # TableView
2 |
3 | In Reactant, using table views is simple. There are TableView classes prepared for drop-in use, suiting most of the use cases. The TableViews are Components so they have their `componentState` and actions depending on the type of TableView.
4 |
5 | Every TableView's component State is a `TableViewState` which is an enum containing these cases:
6 | ```swift
7 | public enum TableViewState {
8 | case items([MODEL])
9 | case empty(message: String)
10 | case loading
11 | }
12 | ```
13 |
14 | In `init` of every TableView, you can configure if the TableView is reloadable and other properties depending on TableView type.
15 |
16 | #### SimpleTableView
17 | `SimpleTableView` is the most generic TableView of them all. Its purpose is to display table with footers and headers. It has three generic parameters: `HEADER` - View component used as headers, `CELL` View component used as cells, `FOOTER` view component used as footers. `MODEL` parameter in this case is a `SectionModel` which binds together component State of section header, array of component States of cells and component State of section footer.
18 |
19 | #### PlainTableView
20 | `PlainTableView` is used for displaying plain table view consisting of cells only. `MODEL` parameter of this TableView's `TableViewState` is component State of the cell.
21 |
22 | Example of using this type of TableView directly as RootView is shown here.
23 | ```swift
24 | class TableViewRootView: PlainTableView, RootView {
25 |
26 | override var edgesForExtendedLayout: UIRectEdge {
27 | return .all
28 | }
29 |
30 | init() {
31 | super.init(cellFactory: LabelView.init,
32 | style: .plain,
33 | reloadable: false)
34 | }
35 | }
36 | ```
37 |
38 | #### HeaderTableView and FooterTableView
39 | These two TableViews show sections with headers only or footers only.
40 |
41 | #### SimulatedSeparatorTableView
42 | This TableView is used for displaying TableView with separators of bigger height than default.
43 |
--------------------------------------------------------------------------------
/ReactantPrototyping/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Source/Core/View/Impl/PickerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PickerView.swift
3 | // Reactant
4 | //
5 | // Created by Matouš Hýbl on 02/04/2018.
6 | // Copyright © 2018 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | #if os(iOS)
12 | public class PickerView: ViewBase, UIPickerViewDataSource, UIPickerViewDelegate {
13 | private let pickerView = UIPickerView()
14 |
15 | public let items: [MODEL]
16 | public let titleSelection: (MODEL) -> String
17 |
18 | public init(items: [MODEL], titleSelection: @escaping (MODEL) -> String) {
19 | self.items = items
20 | self.titleSelection = titleSelection
21 | super.init()
22 | }
23 |
24 | public override func update() {
25 | let title = titleSelection(componentState)
26 | guard let index = items.firstIndex(where: { titleSelection($0) == title }) else { return }
27 | pickerView.selectRow(index, inComponent: 0, animated: true)
28 | }
29 |
30 | public override func loadView() {
31 | children(
32 | pickerView
33 | )
34 |
35 | pickerView.dataSource = self
36 | pickerView.delegate = self
37 | }
38 |
39 | public override func setupConstraints() {
40 | pickerView.snp.makeConstraints { make in
41 | make.edges.equalToSuperview()
42 | }
43 | }
44 |
45 | public func numberOfComponents(in pickerView: UIPickerView) -> Int {
46 | return 1
47 | }
48 |
49 | public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
50 | return items.count
51 | }
52 |
53 | public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
54 | let model = items[row]
55 |
56 | return titleSelection(model)
57 | }
58 |
59 | public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
60 | let model = items[row]
61 |
62 | perform(action: model)
63 | }
64 | }
65 | #endif
66 |
--------------------------------------------------------------------------------
/Source/Core/View/Internal/ControllerRootViewContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ControllerRootViewContainer.swift
3 | // Reactant
4 | //
5 | // Created by Tadeas Kriz on 08/01/16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public final class ControllerRootViewContainer: UIView, Configurable {
12 |
13 | public let wrappedView: UIView?
14 |
15 | public var configuration: Configuration = .global {
16 | didSet {
17 | configuration.get(valueFor: Properties.Style.controllerRoot)(self)
18 | }
19 | }
20 |
21 | public override var frame: CGRect {
22 | didSet {
23 | #if ENABLE_SAFEAREAINSETS_FALLBACK
24 | fallback_computeSafeAreaInsets()
25 | #endif
26 | wrappedView?.frame = bounds
27 | }
28 | }
29 |
30 | #if ENABLE_SAFEAREAINSETS_FALLBACK
31 | public override var bounds: CGRect {
32 | didSet {
33 | fallback_computeSafeAreaInsets()
34 | }
35 | }
36 |
37 | public override var center: CGPoint {
38 | didSet {
39 | fallback_computeSafeAreaInsets()
40 | }
41 | }
42 | #endif
43 |
44 | public required init?(coder aDecoder: NSCoder) {
45 | wrappedView = nil
46 |
47 | super.init(coder: aDecoder)
48 |
49 | loadView()
50 | reloadConfiguration()
51 | }
52 |
53 | public override init(frame: CGRect = .zero) {
54 | wrappedView = nil
55 |
56 | super.init(frame: frame)
57 |
58 | loadView()
59 | reloadConfiguration()
60 | }
61 |
62 | public init(wrap: UIView) {
63 | wrappedView = wrap
64 |
65 | super.init(frame: CGRect.zero)
66 |
67 | addSubview(wrap)
68 | reloadConfiguration()
69 | }
70 |
71 | private func loadView() {
72 | autoresizingMask = [.flexibleWidth, .flexibleHeight]
73 | if frame == CGRect.zero {
74 | frame = window?.bounds ?? UIScreen.main.bounds
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Tests/Core/Component/ComponentBaseTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComponentBase.swift
3 | // Reactant
4 | //
5 | // Created by Matyáš Kříž on 16/11/2017.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 | import RxSwift
13 | import Cuckoo
14 |
15 | class ComponentBaseTest: QuickSpec {
16 | override func spec() {
17 | describe("ComponentBase") {
18 | it("constructs successfully") {
19 | _ = ComponentBase(canUpdate: false)
20 | _ = ComponentBase(canUpdate: true)
21 | _ = ComponentBase<[String], String>(canUpdate: true)
22 | _ = ComponentBase(canUpdate: false)
23 | _ = ComponentBase<[Int], [Int]>(canUpdate: false)
24 | }
25 | it("calls update on init only once when componentState is Void") {
26 | let componentWithUpdate = ComponentBase.spy(canUpdate: true)
27 | let componentNoUpdate = ComponentBase.spy(canUpdate: false)
28 |
29 | verify(componentWithUpdate).update()
30 | verify(componentNoUpdate, never()).update()
31 | }
32 | it("does not call update on init when componentState is not Void") {
33 | let componentWithUpdate = ComponentBase.spy(canUpdate: true)
34 | let componentNoUpdate = ComponentBase.spy(canUpdate: false)
35 |
36 | verify(componentWithUpdate, never()).update()
37 | verify(componentNoUpdate, never()).update()
38 | }
39 | it("calls afterInit on init only once") {
40 | let componentWithUpdate = ComponentBase.spy(canUpdate: true)
41 | let componentNoUpdate = ComponentBase.spy(canUpdate: false)
42 |
43 | verify(componentWithUpdate).afterInit()
44 | verify(componentNoUpdate).afterInit()
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Source/TableView/Identifier/TableViewCellIdentifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewCellIdentifier.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | public struct TableViewCellIdentifier {
12 |
13 | internal let name: String
14 |
15 | public init(name: String = NSStringFromClass(T.self)) {
16 | self.name = name
17 | }
18 | }
19 |
20 | public extension UITableView {
21 |
22 | func register(identifier: TableViewCellIdentifier) {
23 | register(TableViewCellWrapper.self, forCellReuseIdentifier: identifier.name)
24 | }
25 |
26 | func unregister(identifier: TableViewCellIdentifier) {
27 | register(nil as AnyClass?, forCellReuseIdentifier: identifier.name)
28 | }
29 | }
30 |
31 | public extension UITableView {
32 |
33 | func items(with identifier: TableViewCellIdentifier) ->
34 | (_ source: O) -> (_ configureCell: @escaping (Int, S.Iterator.Element, TableViewCellWrapper) -> Void) -> Disposable where O.Element == S {
35 | return rx.items(cellIdentifier: identifier.name)
36 | }
37 |
38 | func dequeue(identifier: TableViewCellIdentifier) -> TableViewCellWrapper {
39 | guard let cell = dequeueReusableCell(withIdentifier: identifier.name) as? TableViewCellWrapper else {
40 | fatalError("\(identifier) is not registered.")
41 | }
42 | return cell
43 | }
44 |
45 | func dequeue(identifier: TableViewCellIdentifier, for indexPath: IndexPath) -> TableViewCellWrapper {
46 | guard let cell = dequeueReusableCell(withIdentifier: identifier.name, for: indexPath) as? TableViewCellWrapper else {
47 | fatalError("\(identifier) is not registered.")
48 | }
49 | return cell
50 | }
51 |
52 | func dequeue(identifier: TableViewCellIdentifier, forRow row: Int, inSection section: Int = 0) -> TableViewCellWrapper {
53 | return dequeue(identifier: identifier, for: IndexPath(row: row, section: section))
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Source/Utils/UIView+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Utils.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 20.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 |
13 | /**
14 | * Adds subviews to the view that this method is called upon.
15 | * Convenience method working the same as `addSubview(_:)` but letting you pass multiple UIViews at once.
16 | * - parameter children: `UIView`s to be added as subviews
17 | * ## Example
18 | * ```
19 | * override func loadView() {
20 | * children(
21 | * titleLabel,
22 | * loginContainer.children(
23 | * loginTextField,
24 | * passwordTextField,
25 | * ),
26 | * passwordRecoveryButton
27 | * )
28 | * }
29 | * ```
30 | */
31 | @discardableResult
32 | public func children(_ children: UIView...) -> UIView {
33 | return self.children(children)
34 | }
35 |
36 | /**
37 | * Adds subviews to the view that this method is called upon.
38 | * Convenience method working the same as `addSubview(_:)` but letting you pass an array of UIViews.
39 | * - parameter children: `UIView`s to be added as subviews
40 | * ## Example
41 | * ```
42 | * override func loadView() {
43 | * children(
44 | * titleLabel,
45 | * loginContainer.children(
46 | * loginTextField,
47 | * passwordTextField,
48 | * ),
49 | * passwordRecoveryButton
50 | * )
51 | * }
52 | * ```
53 | */
54 | @discardableResult
55 | public func children(_ children: [UIView]) -> UIView {
56 | children.forEach(addSubview)
57 | return self
58 | }
59 |
60 | /**
61 | * Variable pointing to view's superview if there is one or self if there isn't.
62 | * Usually used in Controller when passing `Component.componentState` to the corresponding RootView.
63 | */
64 | public var rootView: UIView {
65 | if let superview = superview {
66 | return superview.rootView
67 | } else {
68 | return self
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Source/CollectionView/Implementation/SimpleCollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleCollectionView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 12.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | public enum SimpleCollectionViewAction {
12 | case selected(CELL.StateType)
13 | case cellAction(CELL.StateType, CELL.ActionType)
14 | case refresh
15 | }
16 |
17 | open class SimpleCollectionView: FlowCollectionViewBase> where CELL: Component {
18 |
19 | public typealias MODEL = CELL.StateType
20 |
21 | private let cellIdentifier = CollectionViewCellIdentifier()
22 |
23 | open override var actions: [Observable>] {
24 | #if os(iOS)
25 | return [
26 | collectionView.rx.modelSelected(MODEL.self).map(SimpleCollectionViewAction.selected),
27 | refreshControl?.rx.controlEvent(.valueChanged).rewrite(with: SimpleCollectionViewAction.refresh)
28 | ].compactMap { $0 }
29 | #else
30 | return [
31 | collectionView.rx.modelSelected(MODEL.self).map(SimpleCollectionViewAction.selected)
32 | ]
33 | #endif
34 | }
35 |
36 | private let cellFactory: () -> CELL
37 |
38 | public init(cellFactory: @escaping () -> CELL = CELL.init,
39 | reloadable: Bool = true,
40 | automaticallyDeselect: Bool = true) {
41 | self.cellFactory = cellFactory
42 |
43 | super.init(reloadable: reloadable, automaticallyDeselect: automaticallyDeselect)
44 | }
45 |
46 | open override func loadView() {
47 | super.loadView()
48 |
49 | collectionView.register(identifier: cellIdentifier)
50 | }
51 |
52 | open override func bind(items: Observable<[MODEL]>) {
53 | items
54 | .bind(to: collectionView.items(with: cellIdentifier)) { [unowned self] row, model, cell in
55 | self.configure(cell: cell, factory: self.cellFactory, model: model, mapAction: { SimpleCollectionViewAction.cellAction(model, $0) })
56 | }
57 | .disposed(by: lifetimeDisposeBag)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Source/Validation/Rule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Rule.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 20.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | /**
10 | * Structure used for setting up a validation rule for values of generic type `T`.
11 | * ## Example
12 | * ```
13 | * typealias Human = (name: String, friendly: Bool)
14 | *
15 | * let people: [Human] = [("Adam", true), ("Eva", false), ("Agnes", true), ("Rob", false)]
16 | *
17 | * let friendRule = Rule(validate: { human in
18 | * if human.friendly {
19 | * return nil // no error here, we want to be friends
20 | * } else {
21 | * return FriendError.notFriendly
22 | * }
23 | * })
24 | *
25 | * let friends = people.filter { potentialFriend in
26 | * friendRule.test(potentialFriend)
27 | * }
28 | * - NOTE: There is a ready-made `ValidationError` with `case invalid` ready for you to use if you don't feel like creating your own `Error` enum.
29 | * ```
30 | */
31 | public struct Rule {
32 |
33 | /// Closure used for validating generic value `T`.
34 | public let validate: (T) -> E?
35 |
36 | /**
37 | * Main initializer for `Rule`.
38 | * - parameter validate: closure that is later used in `test(_:)` and `run(_:)` to validate generic value `T`
39 | */
40 | public init(validate: @escaping (T) -> E?) {
41 | self.validate = validate
42 | }
43 |
44 | /**
45 | * Method testing the passed value with the validation closure.
46 | * - parameter value: value to be tested
47 | * - returns: `Bool`, `true` if validation was successful and `false` otherwise
48 | */
49 | public func test(_ value: T) -> Bool {
50 | return validate(value) == nil
51 | }
52 |
53 | /**
54 | * Method running the passed value through the validation closure.
55 | * - parameter value: value to be tested, must be of generic type `T`
56 | * - returns: `Result`, `.success` with the passed value if validation was successful and `.failure` with the error otherwise
57 | */
58 | public func run(_ value: T) -> Result {
59 | if let error = validate(value) {
60 | return .failure(error)
61 | } else {
62 | return .success(value)
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Tests/Configuration/ConfigurationTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurationTest.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 26.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | private typealias Configuration = Reactant.Configuration
14 |
15 | class ConfigurationTest: QuickSpec {
16 |
17 | override func spec() {
18 | describe("Configuration") {
19 | describe("init") {
20 | it("copies configurations preserving last value") {
21 | let configuration1 = Configuration()
22 | configuration1.set(Properties.int, to: 1)
23 | let configuration2 = Configuration()
24 | configuration2.set(Properties.int, to: 2)
25 |
26 | let configuration = Configuration(copy: configuration1, configuration2)
27 |
28 | expect(configuration.get(valueFor: Properties.int)) == 2
29 | expect(configuration.get(valueFor: Properties.string)) == ""
30 | }
31 | }
32 | describe("get/set") {
33 | it("gets and sets value for property") {
34 | let configuration = Configuration()
35 |
36 | configuration.set(Properties.int, to: 1)
37 | configuration.set(Properties.string, to: "A")
38 |
39 | expect(configuration.get(valueFor: Properties.int)) == 1
40 | expect(configuration.get(valueFor: Properties.string)) == "A"
41 | }
42 | }
43 | describe("get") {
44 | it("returns Property.defaultValue if not set") {
45 | let configuration = Configuration()
46 |
47 | expect(configuration.get(valueFor: Properties.int)) == 0
48 | expect(configuration.get(valueFor: Properties.string)) == ""
49 | }
50 | }
51 | }
52 | }
53 |
54 | func apiTest() {
55 | _ = Configuration.global
56 | }
57 |
58 | private struct Properties {
59 |
60 | static let int = Property(defaultValue: 0)
61 | static let string = Property(defaultValue: "")
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Source/Core/Wireframe/UIViewController+Navigation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+Navigation.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 09.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | extension UIViewController {
12 |
13 | /**
14 | * Presents a view controller and returns `Observable` that indicates when the view has been successfully presented.
15 | * - parameter controller: generic controller to present
16 | * - parameter animated: determines whether the view controller presentation should be animated, default is `true`
17 | */
18 | @discardableResult
19 | public func present(controller: C, animated: Bool = true) -> Observable {
20 | let replay = ReplaySubject.create(bufferSize: 1)
21 | present(controller, animated: animated, completion: { replay.onLast() })
22 | return replay.rewrite(with: controller)
23 | }
24 |
25 | /**
26 | * Dismisses topmost view controller and returns `Observable` that indicates when the view has been dismissed.
27 | * - parameter animated: determines whether the view controller dismissal should be animated, default is `true`
28 | */
29 | @discardableResult
30 | public func dismiss(animated: Bool = true) -> Observable {
31 | let replay = ReplaySubject.create(bufferSize: 1)
32 | dismiss(animated: animated, completion: { replay.onLast() })
33 | return replay
34 | }
35 |
36 | @discardableResult
37 | public func present(controller: Observable, animated: Bool = true) -> Observable {
38 | let replay = ReplaySubject.create(bufferSize: 1)
39 | _ = controller
40 | .takeUntil(rx.deallocated)
41 | .flatMapLatest { [weak self] controller in
42 | self?.present(controller: controller).rewrite(with: controller) ?? .empty()
43 | }
44 | .subscribe(
45 | onNext: { [weak self] controller in
46 | guard self != nil else {
47 | replay.onCompleted()
48 | return
49 | }
50 | replay.onNext(controller)
51 | },
52 | onDisposed: {
53 | replay.onCompleted()
54 | })
55 | return replay
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 1.3.1
4 |
5 | * Make sure to call `__rui.updateReactantUI` after `__rui.setupReactantUI` on iOS 13+, tvOS 13+ and macOS 10.15+.
6 |
7 | ## 1.3.0
8 | * Support for Swift 5.0
9 | * Upgraded to RxSwift 5.0
10 | * Min system version increased to:
11 | * iOS 10.0
12 | * tvOS 10.0
13 |
14 |
15 | ## 1.2.0
16 | * Add takeUntil to Wireframe
17 | * Add option to disable automatic cell deselection
18 | * Add ActivityIndicator to subspec groups
19 | * Add reference for create() and rewrite()
20 | * Deprecate ButtonBase with ControlBase
21 | * Add system font support
22 | * Update project for Swift 4.1
23 | * Add generic PickerView
24 | * Add method for stacking UIViews inside UIView
25 | * Fix `rotate` in CGAffineTransformation extensions
26 | * Add `dequeueAndConfigure(identifier:indexPath:factory:model:mapAction:)` to `CollectionView`
27 | * Add better initial configuration of `TableView` and `CollectionView`
28 | * Add styling methods for `UINavigationController` and `UITabBarController`
29 | * Remove RxSwift and SnapKit dependencies from `Configuration` subspec
30 | * Add Observable navigation
31 |
32 | ## 1.1.0
33 | * Add FallbackSafeAreaInsets subspec with a basic implementation of a `safeAreaInsets` and `safeAreaLayoutGuide` fallback for iOS 10.
34 | * Add support for tvOS
35 | * Add reference guide
36 | * Add more docs
37 | * Add tutorials
38 | * Add more tests
39 |
40 | ## 1.0.6
41 | * Fix TableViewBase and CollectionViewBase memory leak.
42 |
43 | ## 1.0.5
44 | * Add create to Wireframe with controller result helper
45 | * Add default implementation to DialogDismissalController
46 | * Add option to have present dialog with result in UINavigationController
47 | * Add styling for DialogControllerBase's `view`
48 | * Possibly breaking: changed `bind(items: [MODEL])` to `bind(items: Observable<[MODEL]>)` in both `TableViewBase` and `CollectionViewBase`. This change was made because RxSwift changed the internals of delegates and dataSources and each `update` caused the TableView/CollectionView to scroll to the beginning.
49 |
50 | ## 1.0.4
51 | * Improved documentation
52 |
53 | ## 1.0.3
54 | * Fixed `TextField` where placeholder didn't have correct position
55 |
56 | ## 1.0.2
57 | * Added `setBackgroundColor:forState` objc alias for ReactantUI
58 |
59 | ## 1.0.1
60 | * Fixed Reactant Example project
61 |
62 | ## 0.6.0
63 |
64 | * Complete API redesign
65 | * Added tests
66 | * Added documentation
67 | * Added changelog
68 |
--------------------------------------------------------------------------------
/Source/Configuration/Configuration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Configuration.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 14.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | /**
10 | * A container for properties. This class is intended for global setup of Reactant internals. At the same time it can be
11 | * instantiated for multiple configurations (for example each part of application can have different background color).
12 | *
13 | * Reactant comes with prebuilt properties which you can find in extensions to the Properties struct. It's recommended
14 | * to add any new property into the Properties struct via an extension to keep properties easily discoverable via auto-complete.
15 | */
16 | public final class Configuration {
17 | // XXX: This code is here due to bug in Swift/Xcode where types inside extensions depend on compilation order
18 | public typealias Style = StyleConfiguration
19 |
20 | /// Global configuration instance. It's used as a default configuration in every component throughout Reactant.
21 | public static var global = Configuration()
22 |
23 | private var data: [Int: Any] = [:]
24 |
25 | /**
26 | * Initializes configuration as a combination of provided configurations.
27 | * If no configuration is provided, an empty Configuration is created.
28 | * - parameter copy: variadic parameter, passing in multiple `Configuration`s merges them into one
29 | */
30 | public init(copy: Configuration...) {
31 | self.data = copy.reduce([:]) { acc, value in
32 | var dictionary = acc
33 | value.data.forEach { dictionary[$0] = $1 }
34 | return dictionary
35 | }
36 | }
37 |
38 | /**
39 | * Standard setter for a value for passed property.
40 | * - parameter property: property to which you want to set the `Configuration`
41 | * - parameter value: value to set for specified property
42 | */
43 | public func set(_ property: Property, to value: T) {
44 | data[property.id] = value
45 | }
46 |
47 | /**
48 | * Standard getter for configuration properties.
49 | * - parameter property: property from which you want to get the `Configuration`
50 | * - returns: value for specified property or a property's default value if it hasn't been set in this `Configuration`
51 | */
52 | public func get(valueFor property: Property) -> T {
53 | return (data[property.id] as? T) ?? property.defaultValue
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Source/CollectionView/Implementation/PagingCollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PagingCollectionView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.02.17.
6 | // Copyright © 2017 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | open class PagingCollectionView: SimpleCollectionView where CELL: Component {
12 |
13 | open override var configuration: Configuration {
14 | didSet {
15 | configuration.get(valueFor: Properties.Style.CollectionView.pageControl)(pageControl)
16 | }
17 | }
18 |
19 | open var showPageControl: Bool {
20 | get {
21 | return pageControl.visibility == .visible
22 | }
23 | set {
24 | pageControl.visibility = newValue ? .visible : .hidden
25 | }
26 | }
27 |
28 | public let pageControl = UIPageControl()
29 |
30 | public override init(cellFactory: @escaping () -> CELL = CELL.init, reloadable: Bool = true, automaticallyDeselect: Bool = true) {
31 | super.init(cellFactory: cellFactory, reloadable: reloadable, automaticallyDeselect: automaticallyDeselect)
32 | }
33 |
34 | open override func loadView() {
35 | super.loadView()
36 |
37 | children(
38 | pageControl
39 | )
40 |
41 | #if os(iOS)
42 | collectionView.isPagingEnabled = true
43 | #endif
44 | collectionView.showsHorizontalScrollIndicator = false
45 | collectionViewLayout.scrollDirection = .horizontal
46 | collectionViewLayout.minimumLineSpacing = 0
47 | }
48 |
49 | open override func setupConstraints() {
50 | super.setupConstraints()
51 |
52 | pageControl.snp.makeConstraints { make in
53 | make.width.equalTo(self)
54 | make.bottom.equalTo(8)
55 | }
56 | }
57 |
58 | open override func bind(items: Observable<[CELL.StateType]>) {
59 | super.bind(items: items)
60 |
61 | items.subscribe(onNext: { [pageControl] items in
62 | pageControl.numberOfPages = items.count
63 | }).disposed(by: lifetimeDisposeBag)
64 | }
65 |
66 | open override func layoutSubviews() {
67 | super.layoutSubviews()
68 |
69 | itemSize = collectionView.bounds.size
70 | }
71 |
72 | open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
73 | pageControl.currentPage = Int(collectionView.contentOffset.x / itemSize.width)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Source/CollectionView/Internal/CollectionViewCellWrapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionViewCellWrapper.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 14.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 |
12 | public final class CollectionViewCellWrapper: UICollectionViewCell, Configurable {
13 |
14 | public var configurationChangeTime: clock_t = 0
15 |
16 | private var cell: CELL?
17 |
18 | public var configuration: Configuration = .global {
19 | didSet {
20 | (cell as? Configurable)?.configuration = configuration
21 | configuration.get(valueFor: Properties.Style.CollectionView.cellWrapper)(self)
22 | }
23 | }
24 |
25 | public override class var requiresConstraintBasedLayout: Bool {
26 | return true
27 | }
28 |
29 | public override var preferredFocusedView: UIView? {
30 | return cell
31 | }
32 |
33 | @available(iOS 9.0, tvOS 9.0, *)
34 | public override var preferredFocusEnvironments: [UIFocusEnvironment] {
35 | return cell.map { [$0] } ?? []
36 | }
37 |
38 | private var collectionViewCell: CollectionViewCell? {
39 | return cell as? CollectionViewCell
40 | }
41 |
42 | public override var isSelected: Bool {
43 | didSet {
44 | collectionViewCell?.setSelected(isSelected)
45 | }
46 | }
47 |
48 | public var configureDisposeBag = DisposeBag()
49 |
50 | public override var isHighlighted: Bool {
51 | didSet {
52 | collectionViewCell?.setHighlighted(isHighlighted)
53 | }
54 | }
55 |
56 | public override func updateConstraints() {
57 | super.updateConstraints()
58 |
59 | cell?.snp.updateConstraints { make in
60 | make.edges.equalTo(contentView)
61 | }
62 | }
63 |
64 |
65 | public func cachedCellOrCreated(factory: () -> CELL) -> CELL {
66 | if let cell = cell {
67 | return cell
68 | } else {
69 | let cell = factory()
70 | (cell as? Configurable)?.configuration = configuration
71 | self.cell = cell
72 | contentView.children(cell)
73 | setNeedsUpdateConstraints()
74 | return cell
75 | }
76 | }
77 |
78 | public func deleteCachedCell() -> CELL? {
79 | let cell = self.cell
80 | cell?.removeFromSuperview()
81 | self.cell = nil
82 | return cell
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Source/TableView/Implementation/PlainTableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlainTableView.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | public enum PlainTableViewAction {
12 | case selected(CELL.StateType)
13 | case rowAction(CELL.StateType, CELL.ActionType)
14 | case refresh
15 | }
16 |
17 | open class PlainTableView: TableViewBase> where CELL: Component {
18 |
19 | public typealias MODEL = CELL.StateType
20 |
21 | private let cellIdentifier = TableViewCellIdentifier| ()
22 |
23 | open override var actions: [Observable>] {
24 | #if os(iOS)
25 | return [
26 | tableView.rx.modelSelected(MODEL.self).map(PlainTableViewAction.selected),
27 | refreshControl?.rx.controlEvent(.valueChanged).rewrite(with: PlainTableViewAction.refresh)
28 | ].compactMap { $0 }
29 | #else
30 | return [
31 | tableView.rx.modelSelected(MODEL.self).map(PlainTableViewAction.selected)
32 | ]
33 | #endif
34 | }
35 |
36 | private let cellFactory: () -> CELL
37 |
38 | public init(
39 | cellFactory: @escaping () -> CELL = CELL.init,
40 | style: UITableView.Style = .plain,
41 | options: TableViewOptions = [])
42 | {
43 | self.cellFactory = cellFactory
44 |
45 | super.init(style: style, options: options)
46 | }
47 |
48 | @available(*, deprecated, message: "This init will be removed in Reactant 2.0")
49 | public init(
50 | cellFactory: @escaping () -> CELL = CELL.init,
51 | style: UITableView.Style = .plain,
52 | reloadable: Bool = true,
53 | automaticallyDeselect: Bool = true)
54 | {
55 | self.cellFactory = cellFactory
56 |
57 | super.init(style: style, reloadable: reloadable, automaticallyDeselect: automaticallyDeselect)
58 | }
59 |
60 | open override func loadView() {
61 | super.loadView()
62 |
63 | tableView.register(identifier: cellIdentifier)
64 | }
65 |
66 | open override func bind(items: Observable<[CELL.StateType]>) {
67 | items
68 | .bind(to: tableView.items(with: cellIdentifier)) { [unowned self] _, model, cell in
69 | self.configure(cell: cell, factory: self.cellFactory, model: model, mapAction: { PlainTableViewAction.rowAction(model, $0) })
70 | }
71 | .disposed(by: lifetimeDisposeBag)
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Source/Core/Styling/Extensions/UIColor+Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Init.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 16.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor {
12 |
13 | /**
14 | * Parses passed hex string and returns corresponding UIColor.
15 | * - parameter hex: string consisting of hex color, including hash symbol - accepted formats: "#RRGGBB" and "#RRGGBBAA"
16 | * - WARNING: This is not a failable **init** (i.e. it doesn't return nil if it fails to parse the passed string, instead it crashes the app) and therefore is NOT recommended for parsing colors received from back-end.
17 | */
18 | public convenience init(hex: String) {
19 | let hexNumber = String(hex.dropFirst())
20 | let length = hexNumber.count
21 | guard length == 6 || length == 8 else {
22 | preconditionFailure("Hex string \(hex) has to be in format #RRGGBB or #RRGGBBAA !")
23 | }
24 |
25 | if let hexValue = UInt(hexNumber, radix: 16) {
26 | if length == 6 {
27 | self.init(rgb: hexValue)
28 | } else {
29 | self.init(rgba: hexValue)
30 | }
31 | } else {
32 | preconditionFailure("Hex string \(hex) could not be parsed!")
33 | }
34 | }
35 |
36 | /**
37 | * Parses passed (Red, Green, Blue) UInt and returns corresponding UIColor, alpha is 1.
38 | * - parameter rgba: UInt of a color - (e.g. 0x33F100)
39 | * - WARNING: Passing in negative number or number higher than 0xFFFFFF is undefined behavior.
40 | */
41 | public convenience init(rgb: UInt) {
42 | if rgb > 0xFFFFFF {
43 | print("WARNING: RGB color is greater than the value of white (0xFFFFFF) which is probably developer error.")
44 | }
45 | self.init(rgba: (rgb << 8) + 0xFF)
46 | }
47 |
48 | /**
49 | * Parses passed (Red, Green, Blue, Alpha) UInt and returns corresponding UIColor.
50 | * - parameter rgba: UInt of a color - (e.g. 0x33F100EE)
51 | * - WARNING: Passing in negative number or number higher than 0xFFFFFFFF is undefined behavior.
52 | */
53 | public convenience init(rgba: UInt) {
54 | let red = CGFloat((rgba & 0xFF000000) >> 24) / 255.0
55 | let green = CGFloat((rgba & 0xFF0000) >> 16) / 255.0
56 | let blue = CGFloat((rgba & 0xFF00) >> 8) / 255.0
57 | let alpha = CGFloat(rgba & 0xFF) / 255.0
58 |
59 | self.init(red: red, green: green, blue: blue, alpha: alpha)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Source/CollectionView/CollectionViewState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionViewState.swift
3 | // Reactant
4 | //
5 | // Created by Filip Dolnik on 15.11.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | /**
10 | * Enum suited for usage with table view as a `Component.componentState`.
11 | *
12 | * `case items([MODEL])` is used for passing the `MODEL` to the table view for setting up the cells
13 | *
14 | * `case empty(message: String)` is used for showing no cells (or dividers)
15 | * and instead putting the `message` value in the center of the screen
16 | *
17 | * `case loading` shows no cells (or dividers) and instead puts an animated loading indicator to the top of the table view
18 | *
19 | * ## Example
20 | * Example's `componentState` is `[Friend]` and its `RootView` is a table view (`PlainTableView` for example).
21 | * ```
22 | * override func afterInit() {
23 | * rootView.componentState = .loading
24 | * }
25 | *
26 | * override func update() {
27 | * if componentState.isEmpty {
28 | * rootView.componentState = .empty("You don't have any friends.")
29 | * } else {
30 | * rootView.componentState = .items(componentState)
31 | * }
32 | * }
33 | * ```
34 | * - NOTE: This enum also contains a method for convenient mapping of `.items`' innards to a different type.
35 | * This state is `Equatable` as long as `.items`' `MODEL` is `Equatable`.
36 | */
37 | public enum CollectionViewState {
38 |
39 | case items([MODEL])
40 | case empty(message: String)
41 | case loading
42 |
43 | /**
44 | * Used to transform items from MODEL to generic T of the same enum `CollectionViewState` using provided closure.
45 | *
46 | * Doesn't affect `case empty(message: String)` or `case loading` in any way.
47 | */
48 | public func mapItems(transform: (MODEL) -> T) -> CollectionViewState {
49 | switch self {
50 | case .items(let items):
51 | return .items(items.map(transform))
52 | case .empty(let message):
53 | return .empty(message: message)
54 | case .loading:
55 | return .loading
56 | }
57 | }
58 | }
59 |
60 | public func == (lhs: CollectionViewState, rhs: CollectionViewState) -> Bool {
61 | switch (lhs, rhs) {
62 | case (.items(let lhsItems), .items(let rhsItems)):
63 | return lhsItems == rhsItems
64 | case (.empty(let lhsMessage), .empty(let rhsMessage)):
65 | return lhsMessage == rhsMessage
66 | case (.loading, .loading):
67 | return true
68 | default:
69 | return false
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reactant
2 |
3 | Reactant is a foundation for rapid and safe iOS development. It allows you to cut down your development costs by improving reusability, testability and safety of your code, especially your UI. Check our our [quick-start guide][quick-start] to learn more.
4 |
5 | [](https://travis-ci.org/Brightify/Reactant)
6 | [][reactant-cocoapods]
7 | [][reactant-cocoapods]
8 | [][reactant-cocoapods]
9 | [][reactant-cocoapods]
10 | [][slack]
11 |
12 | ## Requirements
13 |
14 | * iOS 9.0+
15 | * Xcode 8.0+
16 | * Swift 3.0+
17 |
18 | **NOTE**: We have experimental support for *macOS* platform in `macOS` branch, but it's incomplete and definitely not recommended for production. Our plan is to add full `macOS` support in near future.
19 |
20 | ## Communication
21 | Feel free to reach us on Slack! [https://swiftkit.brightify.org/][slack]
22 |
23 | ## Get Started
24 | Head to our [quick-start guide][quick-start] to learn how Reactant works and what it can do to decrease your development costs!
25 |
26 | ## Versioning
27 | This library uses semantic versioning. We won't introduce any breaking changes without releasing a new major version. Our main goal is to keep the API as stable as possible. We build our applications on top of Reactant as well and we absolutely hate any breaking changes.
28 |
29 | ## Authors
30 | * Tadeas Kriz, [tadeas@brightify.org](mailto:tadeas@brightify.org)
31 | * Matous Hybl, [matous@brightify.org](mailto:matous@brightify.org)
32 | * Filip Dolník, [filip@brightify.org](mailto:filip@brightify.org)
33 |
34 | ## Used libraries
35 |
36 | ### Runtime
37 |
38 | * [Result](https://github.com/antitypical/Result)
39 | * [SnapKit](https://github.com/SnapKit/SnapKit)
40 | * [RxSwift](https://github.com/ReactiveX/RxSwift)
41 | * [RxCocoa](https://github.com/ReactiveX/RxSwift)
42 | * [RxOptional](https://github.com/RxSwiftCommunity/RxOptional)
43 | * [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources)
44 |
45 | ### Tests
46 |
47 | * [Quick](https://github.com/Quick/Quick)
48 | * [Nimble](https://github.com/Quick/Nimble)
49 |
50 | [quick-start]: https://docs.reactant.tech/getting-started/quickstart.html
51 | [reactant-cocoapods]: https://cocoapods.org/pods/Reactant
52 | [slack]: https://swiftkit.brightify.org/
53 |
--------------------------------------------------------------------------------
/Tests/Core/Styling/StyleableTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StyleableTest.swift
3 | // ReactantTests
4 | //
5 | // Created by Filip Dolnik on 18.10.16.
6 | // Copyright © 2016 Brightify. All rights reserved.
7 | //
8 |
9 | import Quick
10 | import Nimble
11 | import Reactant
12 |
13 | class StyleableTest: QuickSpec {
14 |
15 | override func spec() {
16 | var view: UIView!
17 | beforeEach {
18 | view = UIView()
19 | }
20 | describe("applyStyle") {
21 | it("applies style") {
22 | view.apply(style: Styles.background)
23 | view.apply(style: Styles.tint)
24 |
25 | self.assert(view: view)
26 | }
27 | }
28 | describe("applyStyles") {
29 | it("applies styles") {
30 | view.apply(styles: [Styles.background, Styles.tint])
31 |
32 | self.assert(view: view)
33 | }
34 | it("applies styles with vararg") {
35 | view.apply(styles: Styles.background, Styles.tint)
36 |
37 | self.assert(view: view)
38 | }
39 | }
40 | describe("styled") {
41 | it("applies styles") {
42 | let styledView = view.styled(using: [Styles.background, Styles.tint])
43 |
44 | self.assert(view: styledView)
45 | }
46 | it("applies styles with vararg") {
47 | let styledView = view.styled(using: Styles.background, Styles.tint)
48 |
49 | self.assert(view: styledView)
50 | }
51 | }
52 | describe("with") {
53 | it("applies style") {
54 | let styledView = view.with {
55 | $0.backgroundColor = UIColor.blue
56 | $0.tintColor = UIColor.black
57 | }
58 |
59 | self.assert(view: styledView)
60 | }
61 | }
62 | }
63 |
64 | private func assert(view: UIView, file: StaticString = #file, line: UInt = #line) {
65 | XCTAssertEqual(UIColor.blue, view.backgroundColor, file: file, line: line)
66 | XCTAssertEqual(UIColor.black, view.tintColor, file: file, line: line)
67 | }
68 | }
69 |
70 | extension StyleableTest {
71 |
72 | fileprivate struct Styles {
73 |
74 | static func background(_ view: UIView) {
75 | view.backgroundColor = UIColor.blue
76 | }
77 |
78 | static func tint(_ view: UIView) {
79 | view.tintColor = UIColor.black
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Example/Application/Sources/Components/Main/MainRootView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainRootView.swift
3 | // Reactant
4 | //
5 | // Created by Matouš Hýbl on 3/16/17.
6 | // Copyright © 2017 Brightify s.r.o. All rights reserved.
7 | //
8 |
9 | import Reactant
10 | import RxSwift
11 |
12 | enum MainAction {
13 | case updateLabel
14 | case openTable
15 | }
16 |
17 | final class MainRootView: ViewBase, RootView {
18 |
19 | // this property gather's producers of all actions that should be propagated to parent component
20 | override var actions: [Observable] {
21 | return [
22 | updateButton.rx.tap.rewrite(with: .updateLabel),
23 | tableButton.rx.tap.rewrite(with: .openTable)
24 | ]
25 | }
26 |
27 | private let label = LabelView()
28 | private let updateButton = UIButton()
29 | private let tableButton = UIButton()
30 |
31 | private let dateFormatter = DateFormatter()
32 |
33 | override init() {
34 | super.init()
35 |
36 | dateFormatter.timeStyle = .long
37 | }
38 |
39 | override func update() {
40 | label.componentState = dateFormatter.string(from: componentState)
41 | }
42 |
43 | override func loadView() {
44 | children(
45 | label,
46 | updateButton,
47 | tableButton
48 | )
49 |
50 | updateButton.setTitleColor(.white, for: .normal)
51 | updateButton.setTitle("Update", for: .normal)
52 | updateButton.backgroundColor = .blue
53 | updateButton.contentEdgeInsets = UIEdgeInsetsMake(8, 8, 8, 8)
54 |
55 | tableButton.setTitleColor(.white, for: .normal)
56 | tableButton.setTitle("Open table", for: .normal)
57 | tableButton.backgroundColor = .green
58 | tableButton.contentEdgeInsets = UIEdgeInsetsMake(8, 8, 8, 8)
59 | }
60 |
61 | override func setupConstraints() {
62 | label.snp.makeConstraints { make in
63 | make.center.equalToSuperview()
64 | }
65 |
66 | updateButton.snp.makeConstraints { make in
67 | make.centerX.equalToSuperview()
68 | make.leading.greaterThanOrEqualToSuperview().offset(20)
69 | make.trailing.lessThanOrEqualToSuperview().inset(20)
70 |
71 | make.top.equalTo(label.snp.bottom).offset(20)
72 | }
73 |
74 | tableButton.snp.makeConstraints { make in
75 | make.centerX.equalToSuperview()
76 | make.leading.greaterThanOrEqualToSuperview().offset(20)
77 | make.trailing.lessThanOrEqualToSuperview().inset(20)
78 |
79 | make.top.equalTo(updateButton.snp.bottom).offset(20)
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
| | | | | | |