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
MortarAddSubviewsBuilderto 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.,
sidescombines 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.