name: UIKit Horizontal-First Layouting description: Horizontal-first slicing methodology with mandatory ASCII preview for UIKit layout design version: 1.0.0
UIKit Horizontal-First Layouting
A systematic approach to UIKit layout design that prioritizes horizontal slicing and requires ASCII visualization before any code implementation.
Core Methodology (ALWAYS FOLLOW)
- Identify horizontal bands - Slice UI into rows first
- ASCII preview - ALWAYS show layout before writing ANY code
- Implement top-to-bottom - Build each row sequentially
- User confirmation - Get approval before generating code
MANDATORY: ASCII Preview Before Code
CRITICAL: Before writing ANY UIKit layout code, you MUST:
- Draw an ASCII diagram of the entire screen
- Label each horizontal row
- Show content within each row
- Mark fixed vs flexible heights
- Get user confirmation
NEVER skip this step. NEVER write layout code first.
ASCII Preview Format
Full Screen Template
┌─────────────────────────────────────┐
│ [Status Bar - 44pt/54pt] │ <- Safe area top
├─────────────────────────────────────┤
│ [Navigation Bar - 44pt] │ <- Fixed height
├─────────────────────────────────────┤
│ [Row 1: Content description] │ <- Describe content
├─────────────────────────────────────┤
│ [Row 2: Content description] │
├─────────────────────────────────────┤
│ [ │
│ Flexible Content Area │ <- Mark as flexible
│ │
├─────────────────────────────────────┤
│ [Row N: Content description] │
├─────────────────────────────────────┤
│ [Tab Bar / Safe Area - 34pt/49pt] │ <- Safe area bottom
└─────────────────────────────────────┘
Row Detail Template
When a row has horizontal content, show the internal layout:
Row: [Leading | Center | Trailing]
Example:
┌────────────────────────────────────────┐
│ [48px] │ [Flexible] │ [32px] │
│ Icon │ Title + Subtitle│ Chevron │
└────────────────────────────────────────┘
Complete Example: Login Screen
Step 1: ASCII Preview (REQUIRED)
┌─────────────────────────────────────┐
│ [Safe Area Top] │
├─────────────────────────────────────┤
│ [Row 1: Logo - 120pt] │ <- Fixed
│ ┌──────┐ │
│ │ Logo │ │
│ └──────┘ │
├─────────────────────────────────────┤
│ [Row 2: Title - 60pt] │ <- Fixed
│ "Welcome Back" │
├─────────────────────────────────────┤
│ [Row 3: Email Field - 80pt] │ <- Fixed
│ ┌─────────────────────────────┐ │
│ │ Email │ │
│ └─────────────────────────────┘ │
│ [Error label] │
├─────────────────────────────────────┤
│ [Row 4: Password Field - 80pt] │ <- Fixed
│ ┌─────────────────────────────┐ │
│ │ Password [Eye] │ │
│ └─────────────────────────────┘ │
│ [Error label] │
├─────────────────────────────────────┤
│ [Row 5: Forgot Password - 44pt] │ <- Fixed
│ "Forgot Password?" │
├─────────────────────────────────────┤
│ [ │
│ Flexible Spacer │ <- Flexible
│ │
├─────────────────────────────────────┤
│ [Row 6: Login Button - 56pt] │ <- Fixed
│ ┌─────────────────────────────┐ │
│ │ Login │ │
│ └─────────────────────────────┘ │
├─────────────────────────────────────┤
│ [Row 7: Signup Link - 44pt] │ <- Fixed
│ "Don't have account? Sign up" │
├─────────────────────────────────────┤
│ [Safe Area Bottom] │
└─────────────────────────────────────┘
Step 2: Row Breakdown
| Row | Content | Height | Horizontal Layout |
|---|---|---|---|
| 1 | Logo | 120pt fixed | Centered |
| 2 | Title | 60pt fixed | Centered |
| 3 | Email field + error | 80pt fixed | Full width with padding |
| 4 | Password field + error | 80pt fixed | TextField + eye button |
| 5 | Forgot password | 44pt fixed | Centered link |
| Spacer | - | Flexible | - |
| 6 | Login button | 56pt fixed | Full width with padding |
| 7 | Signup link | 44pt fixed | Centered |
Step 3: User Confirms → Generate Code
final class LoginViewController: UIViewController {
// MARK: - UI Elements
private let rootStackView: UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 0
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
// Row 1: Logo
private let logoRow: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let logoImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
// Row 2: Title
private let titleLabel: UILabel = {
let label = UILabel()
label.text = "Welcome Back"
label.font = .systemFont(ofSize: 28, weight: .bold)
label.textAlignment = .center
return label
}()
// Row 3: Email
private let emailRow: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let emailTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "Email"
tf.borderStyle = .roundedRect
tf.keyboardType = .emailAddress
tf.autocapitalizationType = .none
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
private let emailErrorLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12)
label.textColor = .systemRed
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
// Row 4: Password
private let passwordRow: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let passwordTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "Password"
tf.borderStyle = .roundedRect
tf.isSecureTextEntry = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
private let eyeButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "eye"), for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private let passwordErrorLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12)
label.textColor = .systemRed
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
// Row 5: Forgot Password
private let forgotPasswordButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Forgot Password?", for: .normal)
return button
}()
// Flexible Spacer
private let spacerView: UIView = {
let view = UIView()
view.setContentHuggingPriority(.defaultLow, for: .vertical)
return view
}()
// Row 6: Login Button
private let loginRow: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let loginButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Login", for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold)
button.backgroundColor = .systemBlue
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 12
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
// Row 7: Signup Link
private let signupButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Don't have account? Sign up", for: .normal)
return button
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
setupHierarchy()
setupConstraints()
}
// MARK: - Setup
private func setupHierarchy() {
view.addSubview(rootStackView)
// Row 1: Logo
logoRow.addSubview(logoImageView)
rootStackView.addArrangedSubview(logoRow)
// Row 2: Title
rootStackView.addArrangedSubview(titleLabel)
// Row 3: Email
emailRow.addSubview(emailTextField)
emailRow.addSubview(emailErrorLabel)
rootStackView.addArrangedSubview(emailRow)
// Row 4: Password
passwordRow.addSubview(passwordTextField)
passwordRow.addSubview(eyeButton)
passwordRow.addSubview(passwordErrorLabel)
rootStackView.addArrangedSubview(passwordRow)
// Row 5: Forgot Password
rootStackView.addArrangedSubview(forgotPasswordButton)
// Flexible Spacer
rootStackView.addArrangedSubview(spacerView)
// Row 6: Login Button
loginRow.addSubview(loginButton)
rootStackView.addArrangedSubview(loginRow)
// Row 7: Signup Link
rootStackView.addArrangedSubview(signupButton)
}
private func setupConstraints() {
let padding: CGFloat = 24
NSLayoutConstraint.activate([
// Root stack pinned to safe area
rootStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
rootStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
rootStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
rootStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
// Row 1: Logo - 120pt height
logoRow.heightAnchor.constraint(equalToConstant: 120),
logoImageView.centerXAnchor.constraint(equalTo: logoRow.centerXAnchor),
logoImageView.centerYAnchor.constraint(equalTo: logoRow.centerYAnchor),
logoImageView.widthAnchor.constraint(equalToConstant: 80),
logoImageView.heightAnchor.constraint(equalToConstant: 80),
// Row 2: Title - intrinsic (wrapped by stack)
// Row 3: Email - 80pt height
emailRow.heightAnchor.constraint(equalToConstant: 80),
emailTextField.topAnchor.constraint(equalTo: emailRow.topAnchor, constant: 8),
emailTextField.leadingAnchor.constraint(equalTo: emailRow.leadingAnchor, constant: padding),
emailTextField.trailingAnchor.constraint(equalTo: emailRow.trailingAnchor, constant: -padding),
emailTextField.heightAnchor.constraint(equalToConstant: 44),
emailErrorLabel.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 4),
emailErrorLabel.leadingAnchor.constraint(equalTo: emailTextField.leadingAnchor),
emailErrorLabel.trailingAnchor.constraint(equalTo: emailTextField.trailingAnchor),
// Row 4: Password - 80pt height
passwordRow.heightAnchor.constraint(equalToConstant: 80),
passwordTextField.topAnchor.constraint(equalTo: passwordRow.topAnchor, constant: 8),
passwordTextField.leadingAnchor.constraint(equalTo: passwordRow.leadingAnchor, constant: padding),
passwordTextField.trailingAnchor.constraint(equalTo: passwordRow.trailingAnchor, constant: -padding),
passwordTextField.heightAnchor.constraint(equalToConstant: 44),
eyeButton.centerYAnchor.constraint(equalTo: passwordTextField.centerYAnchor),
eyeButton.trailingAnchor.constraint(equalTo: passwordTextField.trailingAnchor, constant: -12),
eyeButton.widthAnchor.constraint(equalToConstant: 24),
passwordErrorLabel.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 4),
passwordErrorLabel.leadingAnchor.constraint(equalTo: passwordTextField.leadingAnchor),
passwordErrorLabel.trailingAnchor.constraint(equalTo: passwordTextField.trailingAnchor),
// Row 5: Forgot Password - 44pt (intrinsic)
forgotPasswordButton.heightAnchor.constraint(equalToConstant: 44),
// Spacer takes remaining space (low hugging priority)
// Row 6: Login Button - 56pt height
loginRow.heightAnchor.constraint(equalToConstant: 56),
loginButton.topAnchor.constraint(equalTo: loginRow.topAnchor),
loginButton.leadingAnchor.constraint(equalTo: loginRow.leadingAnchor, constant: padding),
loginButton.trailingAnchor.constraint(equalTo: loginRow.trailingAnchor, constant: -padding),
loginButton.bottomAnchor.constraint(equalTo: loginRow.bottomAnchor),
// Row 7: Signup Link - 44pt (intrinsic)
signupButton.heightAnchor.constraint(equalToConstant: 44),
])
}
}
Horizontal Slicing Rules
Rule 1: Top-Level is Always Vertical
The root container is a vertical stack. Each child is a horizontal row.
let rootStack = UIStackView()
rootStack.axis = .vertical // ALWAYS
Rule 2: Rows Contain Horizontal Content
Each row manages its own horizontal layout:
Row: [Leading Content | Center Content | Trailing Content]
Rule 3: Fixed vs Flexible Heights
Mark heights clearly in ASCII:
← Fixed 44pt- explicit height constraint← Flexible- uses content hugging/compression resistance
Rule 4: Safe Areas Are Respected
Always show safe area boundaries:
- Top: Status bar + notch
- Bottom: Home indicator
Common Row Patterns
Navigation Row
┌────────────────────────────────────────┐
│ [Back] │ [Title] │ [Action] │
│ 44pt │ Flexible │ 44pt │
└────────────────────────────────────────┘
List Cell Row
┌────────────────────────────────────────┐
│ [Avatar] │ [Title ] │ [Accessory] │
│ 48pt │ [Subtitle ] │ 24pt │
│ │ Flexible │ │
└────────────────────────────────────────┘
Form Field Row
┌────────────────────────────────────────┐
│ [Label] │ [TextField ] │
│ 80pt │ Flexible │
└────────────────────────────────────────┘
Button Bar Row
┌────────────────────────────────────────┐
│ [Cancel] │ [Spacer] │ [Confirm] │
│ Equal │ Flexible │ Equal │
└────────────────────────────────────────┘
Constraint Naming Convention
Use descriptive names that match the ASCII diagram:
// Row names
let headerRow, contentRow, footerRow
// Element positions
let leadingElement, centerElement, trailingElement
// Sections
let topSection, middleSection, bottomSection
Workflow Summary
1. User Request → "Create a settings screen"
↓
2. ASCII Diagram → Draw full screen with all rows
↓
3. Row Breakdown → Table listing content & heights
↓
4. User Confirms → "Looks good" / "Change X"
↓
5. Generate Code → UIKit with proper constraints
Anti-Patterns (NEVER DO)
❌ Write code without ASCII preview ❌ Guess at layout structure ❌ Start with individual views before overall structure ❌ Mix SwiftUI and UIKit for layout ❌ Use Auto Layout without clear row hierarchy
Quick Reference: Heights
| Element | Height |
|---|---|
| Status bar | 44pt (54pt with notch) |
| Navigation bar | 44pt |
| Large title nav | 96pt |
| Tab bar | 49pt |
| Toolbar | 44pt |
| Standard button | 44pt |
| Large button | 56pt |
| Text field | 44pt |
| Table cell | 44pt minimum |
| Safe area bottom | 34pt (home indicator) |
Pre-Layout Interview
Before creating any UI layout, gather requirements using AskUserQuestion:
Layout Questions
Question 1: Screen Type
- Header: "Screen Type"
- Question: "What type of screen is this?"
- Options:
- Form screen - Input fields, labels, buttons
- List screen - Scrollable list of items
- Detail screen - Display content with actions
- Dashboard - Multiple sections/cards
Question 2: Key Components (multiSelect: true)
- Header: "Components"
- Question: "What UI elements do you need?"
- Options:
- Text inputs - Text fields, text views
- Buttons - Action buttons, links
- Images - Photos, icons, avatars
- Lists/Tables - Scrollable content
Question 3: Layout Complexity
- Header: "Complexity"
- Question: "How complex is the layout?"
- Options:
- Simple - Few rows, straightforward
- Medium - Multiple sections, some nesting
- Complex - Many elements, intricate layout
Question 4: Scrolling Behavior
- Header: "Scrolling"
- Question: "Does the content need to scroll?"
- Options:
- No scrolling - Fixed content fits screen
- Vertical scroll - Content exceeds screen height
- Horizontal scroll - Side-scrolling content
- Both directions - Complex scrollable content
Interview Flow
- Ask questions using AskUserQuestion
- Summarize: "Creating a [screen type] with [components], [complexity] layout, [scrolling] behavior"
- ALWAYS show ASCII preview before any code
- Get user confirmation
- Generate UIKit code
Skip Interview If:
- User provided detailed layout specifications
- User says "skip questions" or "just do it"
- User shared wireframe or design