Mortar 3 is incompatible with previous versions, and aims to solve different problems. Check the git tags to find previous versions.
Mortar 3 is still very much a work in progress, and fundamental API decisions may change at any moment. Please let me know If you choose to use this library for a production app so that I can be more cognizant of impact.
Mortar is a Swift DSL (Domain Specific Language) that enables declarative, anonymous view hierarchy construction using UIKit. It bridges the gap between traditional UIKit development and SwiftUI-like syntax while maintaining full compatibility with existing UIKit infrastructure.
- Anonymous View Construction: Create complete view hierarchies without naming views or defining them outside of their usage context
- Declarative Layout: Provides a clean syntax for AutoLayout constraints that works with UIKit's native classes
- Reactive Integration: Seamlessly integrates with CombineEx for reactive programming patterns
- Managed Views: Specialized components for UITableView and UICollectionView that work with model-driven data
import Mortar
class MyViewController: UIViewController {
override func loadView() {
view = UIContainer {
$0.backgroundColor = .darkGray
UIVStack {
$0.alignment = .center
$0.backgroundColor = .lightGray
$0.layout.sides == $0.parentLayout.sideMargins
$0.layout.centerY == $0.parentLayout.centerY
UILabel {
$0.layout.height == 44
$0.text = "Hello, World!"
$0.textColor = .red
$0.textAlignment = .center
}
UIButton(type: .roundedRect) {
$0.setTitle("Button", for: .normal)
$0.handleEvents(.touchUpInside) { NSLog("touched \($0)") }
}
}
}
}
}
- No need to name views or define them outside of their usage context
- Complete layout DSL available for anonymous constraints
- Full UIKit compatibility maintained
Traditional UIKit development requires:
- Explicit view naming and definition
- Verbose AutoLayout constraint code
- Complex separation of concerns for reactive state management
Mortar solves these issues by providing:
- Anonymous view creation with inline layout constraints
- Clean, readable syntax for AutoLayout expressions
- Reactive programming patterns that work naturally with UIKit
Mortar was created to address dissatisfactions with SwiftUI while maintaining the benefits of UIKit. The framework combines the best of both:
- Avoids treating entire view hierarchies as immutable structs
- Maintains UIKit's performance characteristics and flexibility
- Provides clean separation of view logic from business logic
- Enables anonymous, declarative UI construction
- Result Builder Pattern: Uses
MortarAddSubviewsBuilder
to enable anonymous view creation within UIKit's initialization blocks - Layout Properties: Extends UIView with layout properties that provide access to parent and referenced layouts
- Reactive Extensions: Integrates with CombineEx for clean reactive programming patterns
- Managed Views: Provides specialized components for collection views that work with model-driven data
The framework leverages Swift's result builder feature to create a DSL that allows:
- Views to be created and added without explicit naming
- Layout constraints to be expressed in a natural, readable syntax
- Reactive patterns to be applied inline with view construction
- Complex UI hierarchies to be built in a single, declarative block
- DSL allows you to declare layout constraints inline with UIView configuration
- Access to parent layout anchors via
parentLayout
- Multi-constraint guides (e.g.,
sides
combines leading/trailing) - Support for inequalities and constraint modifications
- Layout references for cross-view constraints
// Basic constraint against parent layout
$0.layout.centerY == $0.parentLayout.centerY
// Multi-constraint guide in single expression
$0.layout.sides == $0.parentLayout.sideMargins
// Constraint to constants
$0.layout.size == CGSize(width: 100, height: 100)
// Inequalities
$0.layout.trailing == $0.parentLayout.trailing
// Constraint modification after creation
let group = $0.layout.center == $0.parentLayout.center
group.layoutConstraints.first?.constant += 20
- Integration with CombineEx framework
- Inline event handling and property binding
- Publisher sinking for complex view updates
// Handle UIControl events with CombineEx Actions
$0.handleEvents(.valueChanged, model.toggleStateAction) { $0.isOn }
// Bind publishers to view properties
$0.bind(\.text) <~ model.toggleState.map { "Toggle is \($0)" }
// Sink publishers for complex view updates
$0.sink(model.someVoidPublisher) { view in
// Void publishers handling
}
$0.sink(model.someValuePublisher) { view, value in
// Value publishers handling
}
- Specialized components for UITableView and UICollectionView
- Model-driven data binding
- Automatic view reuse and model updating
// Define model and cell classes
private struct SimpleTextRowModel: ManagedTableViewCellModel {
typealias Cell = SimpleTextRowCell
let text: String
}
private final class SimpleTextRowCell: UITableViewCell, ManagedTableViewCell {
typealias Model = SimpleTextRowModel
}
// Use in view controller
class BasicManagedTableViewController: UIViewController {
override func loadView() {
view = UIContainer {
$0.backgroundColor = .white
ManagedTableView {
$0.layout.edges == $0.parentLayout.edges
$0.sections <~ Property(value: [self.makeSection()])
}
}
}
private func makeSection() -> ManagedTableViewSection {
ManagedTableViewSection(
rows: [
SimpleTextRowModel(text: "Simple row 1"),
SimpleTextRowModel(text: "Simple row 2"),
SimpleTextRowModel(text: "Simple row 3"),
]
)
}
}
- Add Mortar as a dependency in your Package.swift:
dependencies: [
.package(url: "https://github.com/jmfieldman/Mortar.git", from: <version>)
]
- Import Mortar in your code:
import Mortar
- Start building anonymous views with declarative syntax
This project is licensed under the MIT License - see the LICENSE file for details.