We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/dagba/ios-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
---
name: programmatic-uikit-layout
description: Use when building UIKit interfaces without storyboards, setting up Auto Layout constraints with anchors, creating reusable UI components, or encountering layout constraint errors and ambiguous layout warnings
---
# Programmatic UIKit Layout with Auto Layout
## Overview
**Programmatic UIKit layout with anchors provides type-safe, maintainable UI code used by production apps at scale.** Stack views solve 50%+ of layout problems, anchors provide compile-time safety, design systems ensure consistency.
**Core principle:** Anchors-only (no Visual Format Language), stack-first composition, reusable components with centralized spacing/colors.
## Foundation Setup
### Step 1: Disable Storyboards
**Remove Main.storyboard:**
1. Delete Main.storyboard file
2. In Info.plist, delete "Main storyboard file base name" entry
3. Delete "Storyboard Name" in Target → Info → Custom iOS Target Properties
**Configure SceneDelegate:**
```swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = MainViewController()
window?.makeKeyAndVisible()
}
}
```
### Step 2: Essential UIView Setup
**CRITICAL:** Every view needs `translatesAutoresizingMaskIntoConstraints = false`
```swift
// ❌ WRONG: Constraints conflict with autoresizing mask
let label = UILabel()
view.addSubview(label)
label.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
// Runtime error: conflicting constraints
// ✅ CORRECT: Disable autoresizing mask
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
```
**Rule:** Set `translatesAutoresizingMaskIntoConstraints = false` BEFORE adding constraints.
## Layout Anchors (Preferred Method)
### Why Anchors Over NSLayoutConstraint
| Feature | Layout Anchors | NSLayoutConstraint |
|---------|----------------|-------------------|
| Type safety | ✅ Compile-time checks | ❌ Runtime errors |
| Readability | ✅ Fluent API | ❌ Verbose initializer |
| Error prevention | ✅ Can't mix X/Y axes | ❌ Easy to make mistakes |
```swift
// ❌ WRONG: NSLayoutConstraint (verbose, error-prone)
NSLayoutConstraint(
item: label,
attribute: .top,
relatedBy: .equal,
toItem: view,
attribute: .top,
multiplier: 1.0,
constant: 20
).isActive = true
// ✅ CORRECT: Layout anchors (concise, type-safe)
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
```
### Anchor Types
**X-axis:** `leadingAnchor`, `trailingAnchor`, `leftAnchor`, `rightAnchor`, `centerXAnchor`
**Y-axis:** `topAnchor`, `bottomAnchor`, `centerYAnchor`
**Dimension:** `widthAnchor`, `heightAnchor`
**Type safety:**
```swift
// ✅ Compiles: Same axis
label.leadingAnchor.constraint(equalTo: view.leadingAnchor)
// ❌ Compiler error: Mixed axes
label.leadingAnchor.constraint(equalTo: view.topAnchor) // Won't compile!
```
### Basic Constraint Patterns
**Pin to edges:**
```swift
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20)
])
```
**Center with size:**
```swift
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
label.widthAnchor.constraint(equalToConstant: 200),
label.heightAnchor.constraint(equalToConstant: 50)
])
```
**Aspect ratio:**
```swift
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 9.0/16.0) // 16:9
])
```
## Safe Area Layout Guide
**CRITICAL:** Use `safeAreaLayoutGuide` for modern iPhones (notch, dynamic island).
```swift
// ❌ WRONG: Content hidden behind notch
label.topAnchor.constraint(equalTo: view.topAnchor)
// ✅ CORRECT: Respects safe area
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
```
**Standard pattern:**
```swift
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
```
## Stack Views (Stack-First Philosophy)
**CRITICAL:** Stack views solve 50%+ of layout problems. Use them liberally.
```dot
digraph layout_choice {
"Need to arrange multiple views?" [shape=diamond];
"All in line (row/column)?" [shape=diamond];
"Use UIStackView" [shape=box, style=filled, fillcolor=lightgreen];
"Use nested UIStackViews" [shape=box, style=filled, fillcolor=lightblue];
"Use anchors" [shape=box, style=filled, fillcolor=yellow];
"Need to arrange multiple views?" -> "All in line (row/column)?" [label="yes"];
"Need to arrange multiple views?" -> "Use anchors" [label="no"];
"All in line (row/column)?" -> "Use UIStackView" [label="yes"];
"All in line (row/column)?" -> "Use nested UIStackViews" [label="no (complex grid)"];
}
```
### Basic Stack View
```swift
let stackView = UIStackView()
stackView.axis = .vertical // or .horizontal
stackView.spacing = 16
stackView.alignment = .fill // .leading, .center, .trailing
stackView.distribution = .fill // .fillEqually, .equalSpacing, etc.
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
])
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(subtitleLabel)
stackView.addArrangedSubview(button)
```
### Nested Stack Views (Complex Layouts)
```swift
// Horizontal stack with icon + labels
let horizontalStack = UIStackView()
horizontalStack.axis = .horizontal
horizontalStack.spacing = 12
horizontalStack.alignment = .center
let iconImageView = UIImageView(image: UIImage(systemName: "star.fill"))
iconImageView.widthAnchor.constraint(equalToConstant: 24).isActive = true
iconImageView.heightAnchor.constraint(equalToConstant: 24).isActive = true
// Vertical stack for title + subtitle
let verticalStack = UIStackView()
verticalStack.axis = .vertical
verticalStack.spacing = 4
verticalStack.addArrangedSubview(titleLabel)
verticalStack.addArrangedSubview(subtitleLabel)
horizontalStack.addArrangedSubview(iconImageView)
horizontalStack.addArrangedSubview(verticalStack)
view.addSubview(horizontalStack)
// Pin horizontalStack with anchors
```
### Spacer Views
```swift
// Add flexible spacing in stack view
let spacer = UIView()
spacer.setContentHuggingPriority(.defaultLow, for: .vertical)
stackView.addArrangedSubview(spacer)
// Pattern: Button at bottom
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(descriptionLabel)
stackView.addArrangedSubview(spacer) // Pushes button to bottom
stackView.addArrangedSubview(button)
```
## Design System Pattern
### Theme (Centralized Spacing/Colors)
```swift
enum Theme {
enum Spacing {
static let tiny: CGFloat = 4
static let small: CGFloat = 8
static let medium: CGFloat = 16
static let large: CGFloat = 24
static let xLarge: CGFloat = 32
}
enum CornerRadius {
static let small: CGFloat = 4
static let medium: CGFloat = 8
static let large: CGFloat = 12
static let xLarge: CGFloat = 16
}
enum Colors {
static let primary = UIColor.systemBlue
static let secondary = UIColor.systemGray
static let background = UIColor.systemBackground
static let text = UIColor.label
static let textSecondary = UIColor.secondaryLabel
}
}
// Usage:
stackView.spacing = Theme.Spacing.medium
button.layer.cornerRadius = Theme.CornerRadius.medium
label.textColor = Theme.Colors.text
```
### Reusable Components
**Card View:**
```swift
final class CardView: UIView {
private let containerView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) not implemented")
}
private func setupView() {
backgroundColor = Theme.Colors.background
layer.cornerRadius = Theme.CornerRadius.large
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.1
layer.shadowOffset = CGSize(width: 0, height: 2)
layer.shadowRadius = 4
containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor, constant: Theme.Spacing.medium),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Theme.Spacing.medium),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Theme.Spacing.medium),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Theme.Spacing.medium)
])
}
func setContent(_ view: UIView) {
containerView.subviews.forEach { $0.removeFromSuperview() }
view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(view)
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: containerView.topAnchor),
view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
}
```
**Primary Button:**
```swift
final class PrimaryButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setupButton()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) not implemented")
}
private func setupButton() {
backgroundColor = Theme.Colors.primary
setTitleColor(.white, for: .normal)
layer.cornerRadius = Theme.CornerRadius.medium
titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
contentEdgeInsets = UIEdgeInsets(
top: Theme.Spacing.medium,
left: Theme.Spacing.large,
bottom: Theme.Spacing.medium,
right: Theme.Spacing.large
)
}
}
```
## Layout Priority & Content Hugging/Compression
### Content Hugging Priority
**Controls how much view resists growing beyond intrinsic content size.**
```swift
// Default: 250 (low)
// Higher priority = resists growing more
label.setContentHuggingPriority(.required, for: .horizontal) // 1000
button.setContentHuggingPriority(.defaultLow, for: .horizontal) // 250
// Result: Button expands, label stays at intrinsic width
```
### Content Compression Resistance
**Controls how much view resists shrinking below intrinsic content size.**
```swift
// Default: 750 (high)
// Higher priority = resists shrinking more
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal) // 1000
subtitleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) // 250
// Result: Subtitle truncates before title
```
**Common pattern: Label in horizontal stack**
```swift
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.spacing = 8
let label = UILabel()
label.text = "Very long text that might truncate..."
label.setContentHuggingPriority(.defaultLow, for: .horizontal) // Allows expansion
label.setContentCompressionResistancePriority(.required, for: .horizontal) // Prevents shrinking
let button = UIButton()
button.setContentHuggingPriority(.required, for: .horizontal) // Stays at intrinsic width
stackView.addArrangedSubview(label)
stackView.addArrangedSubview(button)
```
## Common Mistakes
| Mistake | Reality | Fix |
|---------|---------|-----|
| "Forgot translatesAutoresizingMaskIntoConstraints = false" | Conflicting constraints, layout breaks | Set to false BEFORE constraints |
| "Using view.topAnchor instead of safeAreaLayoutGuide" | Content hidden behind notch | Always use safeAreaLayoutGuide |
| "Individual .isActive = true for each constraint" | Verbose, inefficient | Use NSLayoutConstraint.activate([]) |
| "Visual Format Language is easier" | VFL is deprecated, error-prone | Use anchors only |
| "Don't need content hugging/compression" | Labels truncate unexpectedly, buttons expand wrong | Set priorities explicitly |
| "Adding subview after constraints" | Crash: view not in hierarchy | addSubview() BEFORE activate() |
## Debugging Layout Issues
### Ambiguous Layout
```swift
// In UIViewController viewDidLoad or custom view init
#if DEBUG
DispatchQueue.main.async {
if self.view.hasAmbiguousLayout {
print("⚠️ Ambiguous layout detected")
self.view.exerciseAmbiguityInLayout() // Animate between valid layouts
}
}
#endif
```
### Constraint Conflicts
**Runtime error:** "Unable to simultaneously satisfy constraints..."
**Debug:**
1. Read error message for constraint IDs
2. Add identifiers to constraints:
```swift
let constraint = label.topAnchor.constraint(equalTo: view.topAnchor)
constraint.identifier = "label-top"
constraint.isActive = true
```
3. Lower priority of less important constraint:
```swift
let constraint = label.heightAnchor.constraint(equalToConstant: 50)
constraint.priority = .defaultHigh // 750 instead of 1000
constraint.isActive = true
```
## Quick Reference
**Basic setup:**
```swift
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
parentView.addSubview(view)
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: parentView.safeAreaLayoutGuide.topAnchor),
view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
])
```
**Stack view:**
```swift
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = Theme.Spacing.medium
stack.addArrangedSubview(view1)
stack.addArrangedSubview(view2)
```
**Design system:**
```swift
view.backgroundColor = Theme.Colors.background
stackView.spacing = Theme.Spacing.medium
button.layer.cornerRadius = Theme.CornerRadius.large
```
## Red Flags - STOP and Reconsider
- Using Visual Format Language → Switch to anchors
- Not disabling `translatesAutoresizingMaskIntoConstraints` → Constraint conflicts
- Ignoring safe area layout guide → Content hidden on modern iPhones
- Creating 5+ constraints manually → Consider UIStackView
- Hard-coded spacing values (8, 16, 24) scattered everywhere → Centralize in Theme
- Activating constraints individually → Batch with activate([])
- Ambiguous layout in production → Add intrinsic size or missing constraints
## Real-World Impact
**Before:** Mixed VFL + anchors + raw NSLayoutConstraint. 500+ lines for profile screen. Frequent constraint conflicts.
**After:** Anchors-only + stack views. 200 lines, zero conflicts, easier to maintain.
---
**Before:** Hard-coded spacing (8px, 12px, 16px) across 30 view controllers. Design update = edit 30 files.
**After:** Theme.Spacing.medium. Design update = edit 1 file, affects entire app.
---
**Before:** Custom complex constraint logic for card layout. 100 lines, hard to debug.
**After:** Reusable CardView component. 30 lines, used in 15 places consistently.
## Sources
- [Mastering iOS auto layout anchors programmatically - The.Swift.Dev.](https://theswiftdev.com/mastering-ios-auto-layout-anchors-programmatically-from-swift/)
- [Effective Auto Layout Programmatically in Swift](https://www.vadimbulavin.com/effective-auto-layout-programmatically-in-swift/)
- [Auto Layout in Swift: Writing constraints programmatically - SwiftLee](https://www.avanderlee.com/swift/auto-layout-programmatically/)
- [Apple Developer Documentation: Auto Layout Guide](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html)