agentsclimarketplace

Uikit layouting

Skill arimunandar/claude-code-ios-plugin/skills/uikit-layouting

Horizontal-first slicing methodology with mandatory ASCII preview for UIKit layout designFrom its SKILL.md

Install
npx -y skills add arimunandar/claude-code-ios-plugin --skill uikit-layouting

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

20.4 KB, ~4.0k tokens by cl100k_base, as published. Nobody here has run it

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)

  1. Identify horizontal bands - Slice UI into rows first
  2. ASCII preview - ALWAYS show layout before writing ANY code
  3. Implement top-to-bottom - Build each row sequentially
  4. User confirmation - Get approval before generating code

MANDATORY: ASCII Preview Before Code

CRITICAL: Before writing ANY UIKit layout code, you MUST:

  1. Draw an ASCII diagram of the entire screen
  2. Label each horizontal row
  3. Show content within each row
  4. Mark fixed vs flexible heights
  5. 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

RowContentHeightHorizontal Layout
1Logo120pt fixedCentered
2Title60pt fixedCentered
3Email field + error80pt fixedFull width with padding
4Password field + error80pt fixedTextField + eye button
5Forgot password44pt fixedCentered link
Spacer-Flexible-
6Login button56pt fixedFull width with padding
7Signup link44pt fixedCentered

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

ElementHeight
Status bar44pt (54pt with notch)
Navigation bar44pt
Large title nav96pt
Tab bar49pt
Toolbar44pt
Standard button44pt
Large button56pt
Text field44pt
Table cell44pt minimum
Safe area bottom34pt (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

  1. Ask questions using AskUserQuestion
  2. Summarize: "Creating a [screen type] with [components], [complexity] layout, [scrolling] behavior"
  3. ALWAYS show ASCII preview before any code
  4. Get user confirmation
  5. Generate UIKit code

Skip Interview If:

  • User provided detailed layout specifications
  • User says "skip questions" or "just do it"
  • User shared wireframe or design

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.