Contents

dimayurkovski/orbitallayout

An Auto Layout DSL for Swift. Chainable, type-safe, wraps `NSLayoutConstraint` directly.

Contents

Install

Swift Package Manager

.package(url: "https://github.com/dimayurkovski/OrbitalLayout.git", from: "1.0.0")

CocoaPods

pod 'OrbitalLayout'

Adding subviews

// parent-side (array form — preferred)
view.orbit(add: label, [.top(16), .leading(16), .trailing(16)])

// parent-side (variadic shorthand)
view.orbit(add: label, .top(16), .leading(16), .trailing(16))

// child-side (array form — preferred)
label.orbit(to: view, [.top(16), .leading(16), .trailing(16)])

// child-side (variadic shorthand)
label.orbit(to: view, .top(16), .leading(16), .trailing(16))

// multiple children — every view is in the hierarchy before the closure runs
view.orbit(avatar, nameLabel, followButton) {
    avatar.orbital.layout(
        .top(24).to(view.safeAreaLayoutGuide, .top),
        .leading(16),
        .size(80)
    )
    nameLabel.orbital.layout(
        .top.to(avatar, .top),
        .leading(12).to(avatar, .trailing),
        .trailing(16)
    )
    followButton.orbital.layout(
        .top(16).to(nameLabel, .bottom),
        .leading(16), .trailing(16),
        .height(44)
    )
}

// already in the hierarchy
contentView.orbital.layout(
    .top(8).to(header, .bottom),
    .leading(16), .trailing(16),
    .height(200)
)

Constraints

// edges
view.orbital.layout(.edges(16))          // all 4 sides
view.orbital.layout(.horizontal(16))     // leading + trailing
view.orbital.layout(.vertical(24))       // top + bottom

// size
iconView.orbital.layout(.size(44))
bannerView.orbital.layout(.size(width: 320, height: 180))
videoView.orbital.layout(.aspectRatio(16.0 / 9.0))

// center
spinnerView.orbital.layout(.center())
badge.orbital.layout(.center(offset: CGPoint(x: 10, y: -5)))

// target another view
subtitle.orbital.layout(
    .top(8).to(titleLabel, .bottom),
    .leading.to(titleLabel, .leading)
)

// relations
descriptionLabel.orbital.layout(.height(120).orLess)
contentView.orbital.layout(.top(8).orMore.to(toolbar, .bottom))

// priority
view.orbital.layout(
    .top(16).priority(.high),
    .height(44).priority(.custom(600))
)

// multiplier
view.orbital.layout(.width.like(superview, 0.4))           // 40% of superview width
view.orbital.layout(.height.like(imageView, .width, 0.5))  // height == imageView.width × 0.5
view.orbital.layout(.height.like(.width, 1.0))             // square

// full chain → single constraint
let c = view.orbital.constraint(.top(16).to(header, .bottom).orMore.priority(.high))
c.constant = 24

Stored constraints

Every constraint is stored by anchor. Named accessors return the .equal constraint.

view.orbital.layout(.top(16), .height(200))

view.orbital.heightConstraint?.constant = 300

// available: topConstraint, bottomConstraint, leadingConstraint, trailingConstraint,
//            widthConstraint, heightConstraint, centerXConstraint, centerYConstraint

// non-equal relations
view.orbital.constraint(for: .width, relation: .lessOrEqual)

Update & remake

// update — change `constant` on existing constraints; no-op if anchor has no stored constraint
view.orbital.update(.top(24), .height(300))
view.orbital.update(.edges(24))

// remake — deactivate and replace constraints for the specified anchors; others untouched
view.orbital.remake(
    .top.to(navigationBar, .bottom),
    .height(120)
)

Activate / deactivate

let constraints = view.orbital.layout(.top(8), .leading(16), .trailing(16))
constraints.deactivate()
constraints.activate()

// deferred activation — named accessors work even while inactive
let pending = view.orbital.prepareLayout(.top(8), .leading(16), .trailing(16))
pending.activate()

Hugging & compression

titleLabel.orbital.hugging(.high, axis: .horizontal)
titleLabel.orbital.compression(.required, axis: .horizontal)

Debug labels

Identifiers show up in Xcode's unsatisfiable-constraints log.

view.orbital.layout(
    .top(16).labeled("card.top"),
    .height(44).labeled("card.height")
)

Sign convention

trailing, bottom, right — auto-negated on same-edge constraints. Always pass a positive value.

view.orbital.layout(
    .trailing(16),                                // view.trailing = superview.trailing − 16
    .bottom(16)                                   // view.bottom   = superview.bottom   − 16
)

// cross-anchor overrides
.trailing(8).to(avatar, .trailing).asOffset      // suppress negation
.bottom(16).to(header, .top).asInset             // force negation

macOS

Identical API. NSView anchors are used automatically. .firstBaseline / .lastBaseline are UIKit-only — using them on macOS is a compile-time error.

License

MIT. See LICENSE.

Package Metadata

Repository: dimayurkovski/orbitallayout

Default branch: main

README: README.md