Describing the layout ergonomically in the code
🏗 Structured Layout API
Mondrian.buildSubviews(on: view) {
VStackBlock {
titleLabel
HStackBlock {
cancelButton
sendButton
}
}
.padding(24)
}
💃 To preserve flexibility of AutoLayout, we have classical style API
Classical Layout API
sendButton.mondrian.layout
.width(120)
.top(.toSuperview)
.trailing(.toSuperview)
.leading(.to(cancelButton).trailing)
.activate()
🤵🏻♂️💭 We guess we still don't cover the all of use-cases. Please feel free to ask what you've faced case in Issues!
This image laid out by MondrianLayout
Layout code
HStackBlock(spacing: 2, alignment: .fill) {
VStackBlock(spacing: 2, alignment: .fill) {
UIView.mock(
backgroundColor: .mondrianRed,
preferredSize: .init(width: 28, height: 28)
)
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 50)
)
UIView.mock(
backgroundColor: .mondrianYellow,
preferredSize: .init(width: 28, height: 28)
)
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
HStackBlock(alignment: .fill) {
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
}
}
VStackBlock(spacing: 2, alignment: .fill) {
HStackBlock(spacing: 2, alignment: .fill) {
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
VStackBlock(spacing: 2, alignment: .fill) {
HStackBlock(spacing: 2, alignment: .fill) {
UIView.mock(
backgroundColor: .mondrianYellow,
preferredSize: .init(width: 28, height: 28)
)
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
}
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
}
}
HStackBlock(spacing: 2, alignment: .fill) {
VStackBlock(spacing: 2, alignment: .fill) {
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
UIView.mock(
backgroundColor: .mondrianBlue,
preferredSize: .init(width: 28, height: 28)
)
}
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
VStackBlock(spacing: 2, alignment: .fill) {
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
}
}
HStackBlock(spacing: 2, alignment: .fill) {
UIView.mock(
backgroundColor: .mondrianRed,
preferredSize: .init(width: 28, height: 28)
)
VStackBlock(spacing: 2, alignment: .fill) {
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
UIView.mock(
backgroundColor: .layeringColor,
preferredSize: .init(width: 28, height: 28)
)
}
}
}
}
.overlay(
UILabel.mockMultiline(text: "Mondrian Layout", textColor: .white)
.viewBlock
.padding(4)
.background(
UIView.mock(
backgroundColor: .layeringColor
)
.viewBlock
)
.relative(bottom: 8, right: 8)
)
- 🌟 Ergonomically Enables us to describe layout by DSL (like SwiftUI's layout).
- 🌟 Automatic adding subviews according to layout representation.
- 🌟 Supports integeration with system AutoLayout API.
- 🌟 Provides classical layout API that describing constraints each view.
A DSL based layout builder with AutoLayout
AutoLayout is super powerful to describe the layout and how it changes according to the bounding box.
What if we get a more ergonomic interface to declare the constraints.
- Optimize the code - still verbose implementation because we're not sure if the API can be stable.
- Brushing up the DSL - to be stable in describing.
- Adding more modifiers for fine tuning in layout.
- Tuning up the stack block's behavior.
- Adding a way to setting constraints independently besides DSL
- AutoLayout is definitely powerful to describe the layout. We might need to set the constraints additionally since DSL can't describe every pattern of the layout.
You can see many layout examples from the demo application.
Simulator.Screen.Recording.-.iPhone.8.-.2021-06-20.at.02.48.38.mp4
MondrianLayout enables us to describe layouts of subviews by DSL (powered by resultBuilders
)
It's like describing in SwiftUI, but this behavior differs a bit since laying out by AutoLayout system.
To describe layout, use buildSubviews
as entrypoint.
This method creates a set of NSLayoutConstraint, UILayoutGuide, and modifiers of UIView.
Finally, those apply. You don't need to call addSubview
. that goes automatically according to hierarchy from layout descriptions.
class MyView: UIView {
let nameLabel: UILabel
let detailLabel: UILabel
init() {
super.init(frame: .zero)
// Seting up constraints constraints, layoutGuides and adding subviews
Mondrian.buildSubviews(on: self) {
VStackBlock {
nameLabel
detailLabel
}
}
// Seting up constraints for the view itself.
Mondrian.layout {
self.mondrian.layout.width(200) // can be method cain.
}
}
}
Sample code assumes run in UIView
. (self is UIView
)
You can replace it with UIViewController.view
.
Attaching to top and bottom safe-area.
Mondrian.buildSubviews(on: self) {
LayoutContainer(attachedSafeAreaEdges: .vertical) {
VStackBlock {
...
}
}
}
or
Mondrian.buildSubviews(on: self) {
VStackBlock {
...
}
.container(respectingSafeAreaEdges: .vertical)
}
Mondrian.buildSubviews(on: self) {
ZStackBlock {
backgroundView.viewBlock.relative(0)
}
}
synonyms:
ZStackBlock(alignment: .attach(.all)) {
backgroundView
}
ZStackBlock {
backgroundView.viewBlock.alignSelf(.attach(.all))
}
Mondrian.layout {
self.mondrian.layout.width(...).height(...)
}
or
self.mondrian.layout.width(...).height(...).activate()
relative(0)
fills to the edges of ZStackBlock
.
Mondrian.buildSubviews(on: self) {
ZStackBlock {
profileImageView.viewBlock.relative(0)
textOverlayView.viewBlock.relative(0)
}
}
ZStackBlock {
myLabel
.relative(.all, .min(20))
}
ZStackBlock {
ZStackBlock {
myLabel
}
.padding(20) /// a minimum padding for the label in the container
}
Alignment
center(default) | leading | trailing | fill |
---|---|---|---|
center(default) | top | bottom | fill |
---|---|---|---|
Mondrian.buildSubviews(on: self) {
VStackBlock(spacing: 4, alignment: alignment) {
UILabel.mockMultiline(text: "Hello", textColor: .white)
.viewBlock
.padding(8)
.background(UIView.mock(backgroundColor: .mondrianYellow))
UILabel.mockMultiline(text: "Mondrian", textColor: .white)
.viewBlock
.padding(8)
.background(UIView.mock(backgroundColor: .mondrianRed))
UILabel.mockMultiline(text: "Layout!", textColor: .white)
.viewBlock
.padding(8)
.background(UIView.mock(backgroundColor: .mondrianBlue))
}
}
Adds a space in stacking layout block.
label
.viewBlock // To enable view describes layout
.padding(8)
.background(backgroundView)
label
.viewBlock // To enable view describes layout
.padding(8)
.overlay(overlayView)
.relative
modifier describes that the content attaches to specified edges with padding.
Not specified edges do not have constraints to the edge. so the sizing depends on intrinsic content size.
You might use this modifier to pin to edge as an overlay content.
ZStackBlock {
VStackBlock {
...
}
.relative(bottom: 8, right: 8)
}
.padding
modifier is similar with .relative
but something different.
Different with that, Not specified edges pin to edge with 0 padding.
ZStackBlock {
VStackBlock {
...
}
.padding(.horizontal, 10) // other edges work with 0 padding.
}
Stacking views in Z axis (aligns in center)
ZStackBlock {
view1
view2
view3
}
Expands to specified edges each view
ZStackBlock(alignment: .attach(.all)) {
view1
view2
view3
}
Specifying alignment each view
ZStackBlock {
view1.viewBlock.alignSelf(.attach(.all))
view2.viewBlock.alignSelf(.attach([.top, .bottom]))
view3.viewBlock.alignSelf(.attach(.top))
}
LayoutManager
does support it.
If we need to change the layout each some conditions such as depending traits, this object helps that.
// TODO: #19
Structured layout API(DSL) does not cover the all of use-cases.
Sometimes we still need a way to describe constraints for a complicated layout.
MondrianLayout provides it as well other AutoLayout libraries.
Activate constraints independently
view.mondrian.layout
.width(10)
.top(.toSuperview)
.right(.toSuperview)
.leading(.toSuperview)
.activate() // activate constraints and returns `ConstraintGroup`
Batch layout**
// returns `ConstraintGroup`
Mondrian.layout {
box1.mondrian.layout
.top(.toSuperview)
.left(.toSuperview)
.right(.to(box2).left)
.bottom(.toSuperview)
box2.mondrian.layout
.top(.toSuperview.top, .exact(10))
.right(.toSuperview)
.bottom(.toSuperview)
}
view.layout.horizontal(.toSuperview, .exact(10))
view.layout.vertical(.toSuperview, .exact(10))
view.layout.edge(.toSuperview)
view.layout.edge(.to(myLayoutGuide))
CocoaPods
pod "MondrianLayout"
SwiftPM
dependencies: [
.package(url: "https://github.com/muukii/MondrianLayout.git", exact: "<VERSION>")
]
MondrianLayout is released under the MIT license.