By the time SwiftUI had matured to the point where I was reaching for it on new projects, I had a long list of things I missed about UIKit — predictable performance, predictable layout, predictable everything. The declarative syntax of SwiftUI was genuinely nice, but the cost of giving up UIKit felt too high. So I decided to see if I could get both.
Mortar 3 is a complete rewrite. It shares basically nothing with Mortar 2 other than the name. The new DSL uses Swift’s result builders to express a UIKit view hierarchy in roughly the same shape you’d write a SwiftUI body:
view = UIContainer {
$0.backgroundColor = .darkGray
UIVStack {
$0.alignment = .center
$0.layout.sides == $0.parentLayout.sideMargins
UILabel {
$0.text = "Hello, World!"
}
UIButton(type: .roundedRect) {
$0.setTitle("Button", for: .normal)
}
}
}
The views are real UIView instances — UILabel is a UILabel, UIVStack is
a UIStackView underneath — so all of the usual UIKit machinery (animations,
gesture recognizers, custom drawing) is still available. Layout uses an inline
constraint syntax that compiles down to standard Auto Layout. Reactive
bindings hook into CombineEx for things like
bind(\.text) <~ model.title or handleEvents(.valueChanged, model.toggleAction).
It worked. It worked well, even. But I ended up walking away from it. As
@Observable and the new Observation framework landed, I found I preferred
designing screens around immutable observable view-models — the pattern I
eventually settled into with SwiftAsyncEx and
SwiftUIEx. Mortar 3 is still up if anyone wants the bridge
between worlds, but I’m not actively using it on new code.