Figure is a elegant declarative UI library, utilizing the Typed Tagless Final solution to the expression problem.
Add github "ZkHaider/Figure" "master"
to your Cartfile, and run Carthage.
- Deployment target iOS 10.0+
- Swift 4+
- Xcode 10+
A view is declared simply by:
let view: iOSRenderer = .view()
You can then render that view like so:
let renderedView: UIView = view.describe.view
You can simply create a var
in your UIViewController
as your root view, like so:
import FigureiOS
final class ViewController: UIViewController {
// MARK: - View
var rootView: iOSRenderer {
return .view()
}
// MARK: - Init
// ...
// MARK: - Lifecycle
override func loadView() {
self.view = self.rootView.describe.view
}
}
Notice we declaratively create the view in rootView
and then render it by calling rootView.describe.view
in loadView()
. Alternatively you can subclass your UIViewController
with RenderViewController
and override the rootView
property. RenderViewController
handles your view rendering automatically, so even less boilerplate to write.
import FigureiOS
final class ViewController: RenderViewController {
// MARK: - View
override var rootView: iOSRenderer {
return .view()
}
}
Declare view hierarchies like so:
let myNestedView: iOSRenderer = .view(
config: [.backgroundColor(.red)],
.view(
layout: [.set(width: 100.0, height: 100.0)],
.view(layout: [.fill]),
.view(),
.view()
)
)
let view: UIView = myNestedView.describe.view
The above will create a root view which has a red background with 1 subview which has a width of 100.0 and a height of 100.0 which itself should have 3 subviews where 1 of those subviews fills the frame of it's parent view.
Figure allows you to use your custom views in case you want more flexibility. Simply use them by:
import FigureiOS
final class MyCustomView: UIView {}
let myCustomView: iOSRenderer = .view(of: MyCustomView.self)
let _myCustomView: UIView = myCustomView.describe.view
let myCustomNestedView: iOSRenderer = .view(
of: MyCustomView.self,
.view(
.view(),
.view(
of: MyCustomView.self,
config: [.backgroundColor(.red)]
)
)
)
let _myCustomNestedView: UIView = myCustomNestedView.describe.view
This library adheres to be as composable as possible, meaning it should be easy for someone to:
- Add a new type
- Add a new interpretation or operation
This example is shown by the FigureSerializable
module. We've implemented a new target and added new functionality to our ViewDescriptor
. If someone wanted to now make our view hierarchy codable you can easily do so now via:
import FigureSerializable
let view: iOSRenderer = .view()
guard
let json = view.describe.json
else { return }
// use json...
Notice we are importing a new module FigureSerializable
. Here is the rendered JSON:
Figure
welcomes both fixes, improvements, and feature additions. If you'd like to contribute, open a pull request with a detailed description of your changes.
As a rule of thumb, if you're proposing an API breaking change or a change to existing functionality, consider proposing it by opening an issue, rather than a pull request; we'll use the issue as a public forum for discussing whether the proposal makes sense or not.
Haider Khan
Timothy Kautz