Skip to content

danielinoa/Pin

Repository files navigation

Pin

Pin logo

Declarative UIKit view hierarchy and Auto Layout, in one small package.

CI status Swift tools 6.0 iOS 13 or later Pre-1.0 status

Pin lets UIKit code describe containment and constraints together. Build nested layouts with a result builder, activate the complete tree in one call, and use the built-in lifecycle to cleanly reverse constraints, layout guides, and containment state. It has no third-party dependencies.

Requirements

Component Requirement
iOS 13.0 or later
Xcode 16 or later
Swift toolchain Swift 6 / Swift tools 6.0 or later
Language mode Swift 5 compatibility mode or Swift 6

Swift 5 compatibility refers to the language mode supported by a Swift 6 toolchain; Pin does not support Swift 5 toolchains. UIKit-facing APIs are isolated to @MainActor.

Installation

Important

Pin is pre-1.0 and does not have a stable release tag yet. The following dependency tracks the moving main branch. Pin a full commit revision when you need reproducible builds.

In Xcode, choose File → Add Package Dependencies…, enter https://github.com/danielinoa/Pin.git, select Branch with main, and add the Pin product to your app target.

For a Swift package, declare both the package and target dependency:

dependencies: [
    .package(url: "https://github.com/danielinoa/Pin.git", branch: "main")
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [.product(name: "Pin", package: "Pin")]
    )
]

Pin will adopt semantic versioning with its first tagged release.

Quick start

Store the layout description when you may need to invalidate, deactivate, or replace it later.

import Pin
import UIKit

@MainActor
final class ProfileViewController: UIViewController {
    private let avatarView = UIImageView()
    private let nameLabel = UILabel()
    private var layout: Pinnable?

    override func viewDidLoad() {
        super.viewDidLoad()

        avatarView.backgroundColor = .secondarySystemBackground
        avatarView.layer.cornerRadius = 36
        avatarView.clipsToBounds = true
        nameLabel.text = "Taylor"

        layout = view.add {
            avatarView
                .size(square: 72)
                .pin(to: .leadingMargin, .topMargin)

            nameLabel
                .pin(\.leadingAnchor, to: avatarView.trailingAnchor, constant: 16)
                .pin(\.centerYAnchor, to: avatarView.centerYAnchor)
                .pin(\.trailingAnchor, to: view.layoutMarginsGuide.trailingAnchor)
        }
        layout?.activate()
    }

    func removeLayout() {
        layout?.deactivate()
        layout = nil
    }
}

add inserts both views before resolving their constraints. There is no separate addSubview phase.

API at a glance

Goal API
Build a child hierarchy parent.add { child; nestedLayout }
Pin ordinary edges pin(to: .edges) or .horizontalEdges / .verticalEdges
Pin layout-margin edges pin(to: .marginEdges) or its horizontal / vertical groups
Match parent size or center pin(to: .size), pin(to: .center), or combine groups with +
Set a fixed size size(square:), size(width:height:), size(width:), size(height:)
Tune a relationship pin(to:relation:multiplier:padding:priority:)
Pin anchors or safe-area anchors pin(\.topAnchor, to: targetAnchor, constant:priority:)
Center between two anchors center(between:and:) for either axis
Add arranged subviews stackView.stack { arrangedViews }
Manage the layout activate(), invalidate(), and deactivate()

Positive padding moves top and leading edges inward; Pin reverses its sign for bottom and trailing edges to produce a uniform inset.

Recipes

Pin to the safe area

Anchor pinning works with any compatible UIKit anchor, including guides owned by another view.

import Pin
import UIKit

let container = UIView()
let content = UIView()
let layout = container.add {
    content
        .pin(\.topAnchor, to: container.safeAreaLayoutGuide.topAnchor, constant: 16)
        .pin(\.leadingAnchor, to: container.safeAreaLayoutGuide.leadingAnchor, constant: 16)
        .pin(\.trailingAnchor, to: container.safeAreaLayoutGuide.trailingAnchor, constant: -16)
        .pin(\.bottomAnchor, to: container.safeAreaLayoutGuide.bottomAnchor, constant: -16)
}

layout.activate()

Anchor constants follow UIKit's sign convention, which is why the trailing and bottom insets above are negative. The semantic padding argument to pin(to:) handles that sign adjustment automatically.

Compose dynamic stack content

The builder supports conditionals, optionals, arrays, and loops. stack uses arranged-subview containment rather than ordinary addSubview containment.

import Pin
import UIKit

let stackView = UIStackView()
let titleLabel = UILabel()
let detailLabel = UILabel()
let showsDetails = true
let layout = stackView.stack {
    titleLabel
    if showsDetails {
        detailLabel
    }
}

layout.activate()

Lifecycle guarantees

  • activate() composes the hierarchy before resolving constraints. Repeated activation is a no-op.
  • deactivate() reverses activation order, including constraints, temporary layout guides, built-in containment, previous view or arranged-subview placement, and translatesAutoresizingMaskIntoConstraints values. Repeated deactivation is a no-op.
  • A deactivated layout can be activated again. Each activation records a fresh set of reversible effects.
  • An active description is immutable. A view may appear only once in a tree, and a node cannot belong to two active trees.

The built-in add() and stack() strategies restore hierarchy state they change. A custom containment strategy receives no automatic guarantee for its own side effects; its returned closure must completely undo them. Resolver authoring, custom containment, control-flow details, and lifecycle invariants are covered in the advanced guide.

Project

License

A license has not yet been selected. Until a license file is added, copyright law reserves all rights to the author.

Pin is primarily the work of Daniel Inoa.

About

A declarative wrapper around NSLayoutConstraint and NSLayoutAnchor to simplify the assembly of AutoLayout constraints.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

2 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors