UIKit Horizontal-First Layouting — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited UIKit Horizontal-First Layouting (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A systematic approach to UIKit layout design that prioritizes horizontal slicing and requires ASCII visualization before any code implementation.
CRITICAL: Before writing ANY UIKit layout code, you MUST:
NEVER skip this step. NEVER write layout code first.
┌─────────────────────────────────────┐
│ [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
└─────────────────────────────────────┘When a row has horizontal content, show the internal layout:
Row: [Leading | Center | Trailing]
Example:
┌────────────────────────────────────────┐
│ [48px] │ [Flexible] │ [32px] │
│ Icon │ Title + Subtitle│ Chevron │
└────────────────────────────────────────┘┌─────────────────────────────────────┐
│ [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] │
└─────────────────────────────────────┘| 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 |
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),
])
}
}The root container is a vertical stack. Each child is a horizontal row.
let rootStack = UIStackView()
rootStack.axis = .vertical // ALWAYSEach row manages its own horizontal layout:
Row: [Leading Content | Center Content | Trailing Content]Mark heights clearly in ASCII:
← Fixed 44pt - explicit height constraint← Flexible - uses content hugging/compression resistanceAlways show safe area boundaries:
┌────────────────────────────────────────┐
│ [Back] │ [Title] │ [Action] │
│ 44pt │ Flexible │ 44pt │
└────────────────────────────────────────┘┌────────────────────────────────────────┐
│ [Avatar] │ [Title ] │ [Accessory] │
│ 48pt │ [Subtitle ] │ 24pt │
│ │ Flexible │ │
└────────────────────────────────────────┘┌────────────────────────────────────────┐
│ [Label] │ [TextField ] │
│ 80pt │ Flexible │
└────────────────────────────────────────┘┌────────────────────────────────────────┐
│ [Cancel] │ [Spacer] │ [Confirm] │
│ Equal │ Flexible │ Equal │
└────────────────────────────────────────┘Use descriptive names that match the ASCII diagram:
// Row names
let headerRow, contentRow, footerRow
// Element positions
let leadingElement, centerElement, trailingElement
// Sections
let topSection, middleSection, bottomSection1. 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❌ 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
| 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) |
Before creating any UI layout, gather requirements using AskUserQuestion:
Question 1: Screen Type
Question 2: Key Components (multiSelect: true)
Question 3: Layout Complexity
Question 4: Scrolling Behavior
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.