Declarative, state-driven UI framework
Cyanic is an iOS framework created at Feil, Feil, & Feil GmbH in response to a need for state-driven UI. It borrows heavily
from the concepts of Airbnb’s MvRx framework (which our Android developers use) to create a very similar
code base with Android thereby unifying the business logic in both platforms. We use this framework to create complex, performant, and reactive
screens in our projects.
Cyanic is a Swift only framework. There are no plans to make it compatible with Objective-C.
Requirements:
Add the following to your Podfile:
pod 'Cyanic'
pod 'LayoutKit', :git => 'https://github.com/hooliooo/LayoutKit.git' // Use this fork until LayoutKit is updated
Integrate your dependencies using frameworks: add use_frameworks!
to your Podfile.
pod install
.LayoutKit is the library that is responsible for most of the UI logic in Cyanic. However, as of April 17, 2019, there are some limitations to the current LayoutKit version in Cocoapods:
Without these changes, Cyanic will continue to use the forked version.
Check out our wiki for full documentation.
A very simple example with expandable functionality:
struct YourState: ExpandableState {
enum Section: String, CaseIterable {
case first
case second
}
static var `default`: YourState {
return YourState(
text: "Hello, World!",
expandableDict: YourState.Section.allCases.map { $0.rawValue }
.reduce(into: [String: Bool](), { (current: inout [String: Bool], id: String) -> Void in
current[id] = false
}
)
}
var text: String
var expandableDict: [String: Bool]
}
class YourViewModel: ViewModel<YourState> {
func showCyanic() {
self.setState { $0.text = "Hello, Cyanic!" }
}
}
class YourComponentViewController: SingleSectionCollectionComponentViewController {
private let viewModel: YourViewModel = YourViewModel(initialState: YourState.default)
override var viewModels: [AnyViewModel] {
return [self.viewModel.asAnyViewModel]
}
override func buildComponents(_ componentsController: inout ComponentsController) {
withState(self.viewModel) { (state: YourState) -> Void in
componentsController.staticTextComponent {
$0.id = "title"
$0.text = state.text
}
componentsController.buttonComponent {
$0.id = "button"
$0.onTap = { [weak self]
self?.viewModel.showCyanic()
}
}
let firstExpandableID: String = YourState.Section.first.rawValue
let yourExpandable = components.expandableComponent { [weak self] in
guard let s = self else { return }
$0.id = firstExpandableID
$0.contentLayout = LabelContentLayout(text: Text.unattributed("Hello, World!"))
$0.isExpanded = state.expandableDict[firstExpandableID] ?? false
$0.setExpandableState = self.viewModel.setExpandableState
$0.backgroundColor = UIColor.lightGray
$0.height = 55.0
}
// These ButtonComponents will only show up when yourExpandable is expanded.
if yourExpandable.isExpanded {
for number in 1...5 {
componentsController.buttonComponent {
$0.id = "button\(number)"
$0.title = "\(number)"
$0.onTap = { [weak self]
print("Hello, World from Button \(number)")
}
}
}
}
}
}
}