Author: Mislav Javor.
This project is currently a one man operation. I've dedicated a large chuck of
my free time to this, and would be immensely grateful for any help.
Thank you for contributing ❤️
- Storyboards are hard to maintain, obfuscate functionality, hard to reuse
and almost impossible to merge.
- Writing in code is extremely verbose, time consuming and lacks a high level overview of the layout
Kandinsky combines the expressiveness of storyboards with the power of Swift language.
- Swift Powered DSL
- Easy to write and read
- Modular (build reusable components)
- Reactive (RxSwift/ReactiveCocoa) friendly
- Interactive prototyping using playgrounds
- Easy to merge
- Turing complete (
for
loops,if
statemets, protocols, interitance etc...) layout code
If we write this:
UIView.set {
$0.id = "root"
$0.view.backgroundColor = .lightGray }.add { r in
UIButton.set {
$0.id = "myButton"
$0.view.setTitle(buttonTitle, for: .normal)
$0.view.setTitleColor(.black, for: .normal)
$0.centerInParent()
}/r
}
We get this:
- iOS 9.0+
- Xcode 8.0+
CocoaPods is a dependency manager for Cocoa projects.
To install Kandinsky, simply add the following line to your Podfile:
pod 'Kandinsky', '~> 1.0.1'
Carthage support coming soon
First make sure to import Kandinsky
.
The syntax for the DSL is really simple and intuitive. After you've imported
Kandinsky
, any class inheriting from UIView
(e.g. UIButton
, UILabel
)
will have a set
method exposed like so:
UIView.set { (param: Kandinsky<UIView>) -> Void in
/*
The ID of the view, which can later be used for styling
and querying
*/
param.id = "<id>"
/*
The `view` property is the instance of the view which
is being created (e.g. if you're creating a UIButton the `view`
property would be a UIButton)
*/
param.view.backgroundColor = .red
}
This takes care of view creation and customization. The next step is to
add subviews. We can set this by calling the add
method (note: unlike
set
, add
is not static and must be called after the set
block)
UIView.set {
$0.id = "root"
$0.view.backgroundColor = .black }.add { r in // calling the `add` method, `r` is placeholder for `root`
UIButton.set {
$0.id = "demoButton"
$0.view.setTitle("Push me!", for: .normal)
/*
The framework exposes many methods for adding constraints
like `centerInParent`, `alignParentLeading`, `under(id:)`,
`toLeftOf(:)`. You can also easily create your own constraint
helper methods
*/
$0.centerInParent()
}/r // The `/` operator adds the left side item to the right side item
// in this case it means it adds the Kandinsky<UIButton> to the `r`
// variable which we declared above and which is an instance of
// Kandinsky<UIView>. The `/` operator can add any two elements
// of type `Canvas to one another`
}
Here is a simple example:
let layout =
UIView.set {
$0.view.backgroundColor = .white }.add { r in
UILabel.set {
$0.id = "titleLabel"
$0.view.text = "Hello world"
$0.fontSize = 30 // fontSize is a helper function
$0.centerInParent()
}/r
UIButton.set {
$0.view.setTitle("Push me!", for: .normal)
$0.view.setTitleColor(.blue, for: .normal)
$0.under("titleLabel")
$0.centerHorizontallyInParent()
}/r
}
This produces a view that looks like this:
In order to use your layout, simply make your UIViewController
implement the
Controller
protocol. This means adding the didRender(ViewHolder:root:)
method
to your UIViewController
.
Then in the loadView
method of your UIViewController
,
call the setContentView
function and pass the instance of your layout
The didRender
method will be called after all of the views have been added
and constraints set.
You can use it to extract views from the ViewHolder
by using the
let myView = views["<view_id>"] as? UIButton // cast to your specific view
class DemoVC: UIViewController, Controller {
var views: ViewHolder = [:]
override func loadView() {
super.loadView()
setContentView(with: layout)
}
func didRender(views: ViewHolder, root: AnyCanvas) {
self.views = views
let button = views["pressMeButton"] as? UIButton
button?.addTarget(self, action: #selector(didTouchButton), for: .touchUpInside)
}
func didTouchButton() {
let title = views["titleLabel"] as? UILabel
title?.text = "Pressed the button"
PlaygroundHelper.alert(over: self, message: "Pressed the button")
}
}
Note - setContentView
only sets the view
property of the UIViewController
and
calls the didRender
method. You can call it at any time, but it's recommended to
call it in the loadView
method
If you don't want to inherit the Controller
and just want the view from your
canvas, you can do it like this:
let view = CanvasRenderer.render(demoLayout)
This framework is build by following the latest and greatest in the protocol
oriented world of Swift. If you wish to add additional functionality, you only
need to extend the Canvas
protocol
extension Canvas {
func alignParentLeadingAndTrailing(offset: Int) {
// If you're working with constraints - you must append your code
// to the `deferAfterRender` array. Otherwise your app will fail
deferToAfterRender.append({ views in
self.view.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(offset)
make.trailing.equalToSuperview().offset(-offset)
}
})
}
}
And after you've done that you can call it:
...
UIButton.set {
...
$0.alignParentLeadingAndTrailing(offset: 20)
...
}
...
You can also be more specific:
extension Canvas where UIKitRepresentation == UITableView {
func setDelegateAndDataSource<T>(item: T)
where T: UITableViewDelegate, T: UITableViewDataSource {
self.view.delegate = item
self.view.dataSource = item
}
}
extension Canvas where UIKitRepresentation: UILabel {
func setTextToLoremIpsum() {
self.view.text = "Lorem ipsum dolor sit..." // ...
}
}
And then those properties will only appear on those types of Canvas
es
UITableView.set {
$0.setDelegateAndDataSource(item: delegate)
}
UILabel.set {
$0.setTextToLoremIpsum()
}
- If you want to contribute please feel free to submit pull requests.
- If you have a feature request please open an issue.
- If you found a bug or need help please check older issues, FAQ and threads on StackOverflow (Tag 'Kandinsky') before submitting an issue..
Before contribute check the CONTRIBUTING file for more info.
Follow these 3 steps to run Example project:
- Clone Kandinsky repository
- Open
Kandinsky.xcworkspace
- Run the
Example
project
OR
- Open the
Example/Playground
and play around with live-preview
This can be found in the CHANGELOG.md file.