Draftsman is a Layout builder based on AutoLayout with Declarative approach

Overview

Draftsman

Draftsman is a DSL framework for Swift focussed on builder pattern

codebeat badge build test SwiftPM Compatible Version License Platform

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

  • Swift 5.3 or higher (Swift 5.1 for version 1.1.1 or lower)
  • iOS 10.0 or higher
  • XCode 12.5 or higher (XCode 11 for version 1.1.1 or lower)

Installation

Cocoapods

Draftsman is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Draftsman', '~> 2.0.4'

or for Swift 5.1 and XCode 11

pod 'Draftsman', '~> 1.1.1'

Swift Package Manager from XCode

  • Add it using XCode menu File > Swift Package > Add Package Dependency
  • Add https://github.com/hainayanda/Draftsman.git as Swift Package URL
  • Set rules at version, with Up to Next Major option and put 2.0.3 or 1.1.1 for Swift 5.1 and XCode 11 as its version
  • Click next and wait

Swift Package Manager from Package.swift

Add as your target dependency in Package.swift

dependencies: [
    .package(url: "https://github.com/hainayanda/Draftsman.git", .upToNextMajor(from: "2.0.4"))
]

or for Swift 5.1 and XCode 11

dependencies: [
    .package(url: "https://github.com/hainayanda/Draftsman.git", .upToNextMajor(from: "1.1.1"))
]

Use it in your target as Draftsman

 .target(
    name: "MyModule",
    dependencies: ["Draftsman"]
)

Author

Nayanda Haberty, [email protected]

License

Draftsman is available under the MIT license. See the LICENSE file for more info.


Basic Usage

Draftsman is NSLayoutConstraints and UIView hierarchy builder. Draftsman using new resultBuilder from Swift that makes the Declarative approach possible. For 1.1.1 version, please lookup this readme instead


Basic

Creating constraints is very easy. All you need to do is call plan to get LayoutScheme:

myView.plan
    .left(.equal, to: otherView.rightAnchor)
    .right(.equalTo(16), to: .parent)
    .top(.lessThanTo(8), to: .safeArea)
    .bottom(.moreThan, to: .top(of: .keyboard))
    .apply()

there are two method to end planning constraints which can be called from both any UIView or UIViewController:

  • func apply() -> [NSLayoutConstraint]
  • func build() -> [NSLayoutConstraint]

the difference between the two is apply will activate the constraints but build will only create constraints without activating it. Apply return value is discardable so it's optional for you to use the created NSLayoutConstraint or not.

You could always create a UIViewController or UIView and implements Planned protocol, and call applyPlan() whenever you want the plan to be applied:

import Draftsman

class MyViewController: UIViewController, Planned {
    
    @LayoutPlan
    var viewPlan: ViewPlan {
        UIStackView(axis: .vertical, spacing: 32).plan
            .center(.equal, to: .parent)
            .horizontal(.equalTo(16), to: .safeArea)
            .vertical(.moreThanTo(16), to: .safeArea)
            .insertStacked {
                MyView()
                MyOtherView()
                SomeOtherView()
            }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        applyPlan()
    }
}

View Hierarchy

You can create view hierarchy while creating a constraints by using planContent method and insert method for subview plan (planStackedContent and insertStacked if its arranged subviews in UIStackView). You should not calls apply or build when using this method, since it will always automatically populate all constraints inside to activate as bulk:

view.planContent {
    UIView().plan
        .center(.equal, to: .parent)
        .horizontal(.equal, to: .safeArea)
        .vertical(.moreThan, to: .safeArea)
        .insert {
            myView.plan
                .edges(.equal, to: .parent)
        }
}

The hierarchy of View is just like how the closure is declared in your code. The above code, It actually will do the following instruction sequentially:

  1. view create and insert new UIView()
  2. new UIView then will create constraints
  3. new UIView then will insert myView
  4. myView then will create constraints
  5. all the constraints then will be activated

So if the hierarchy is written in pseudo hierarchy style, it should be similar to this:

view
|____new UIView
|    |____myView

the compatible type to be passed in the closure are:

  • any descendant of UIView
  • any descendant of UIViewController

If you pass UIViewController, it will be automatically added UIViewController view as a child and put the UIViewController as a child of its current UIViewController. You could insert components as much as you need, it will fit all the Views just like how you write it.


PlanDelegate

You could pass PlanDelegate to delegate some problem which could be occurs when you are planning. The delegate is declared like this:

public protocol PlanDelegate: class {
    func planer(viewHaveNoSuperview view: UIView) -> UIView?
    func planer(neededViewControllerFor viewController: UIViewController) -> UIViewController?
    func planer(_ view: UIView, errorWhenPlanning error: DraftsmanError)
}
  • planer(viewHaveNoSuperview:) will be called if the planner needs superview but cannot find any. You could provide one, or just return nil to ignore those plan and produce error which can be caught at this delegate. The default is nil.
  • planer(neededViewControllerFor:) will be called if the planner needs parent UIViewController but cannot find any. You could provide one, or just return nil to ignore those plan and produce error which can be caught at this delegate. The default is to get the current view UIViewController if have any.
  • planer(_:, errorWhenPlanning:) will be called if any error occurs when planning.
view.planContent(withDelegate: myDelegate) {
    UIView().plan
        .center(.equal, to: .parent)
        .horizontal(.equal, to: .safeArea)
        .vertical(.moreThan, to: .safeArea)
        .insert {
            myView.plan
                .edges(.equal, to: .parent)
        }
}

View Position

Basic Positioning

Positioning a View is easy. You just need to declare which anchor should have relation to others:

myView.plan
    .top(.equal, to: other.topAnchor)
    .right(.equalTo(8), to: other.rightAnchor)
    .bottom(.moreThan, to: other.bottomAnchor)
    .left(.moreThanTo(8), to: other.leftAnchor)
    .centerX(.lessThan, to: other.centerXAnchor)
    .centerY(.lessThanTo(8), to: other.centerYAnchor)

the anatomy of position plan is:

func anchor_name(_ relation: LayoutRelation, to anchor: other_anchor, priority: UILayoutPriority? = nil) -> Self

which anchor_name could be:

  • top
  • bottom
  • left
  • right
  • centerX
  • centerY

and LayoutRelation is enumeration with generic param which in this case is CGFloat:

  • moreThanTo(CGFloat)
  • lessThanTo(CGFloat)
  • equalTo(CGFloat)
  • moreThan
  • lessThan
  • equal

all the parameter is CGFloat that will be translated as space towards the anchor, as when the anchor is left, then the param will be translated as space to the left, and if the anchor is right, then the param will be translated as space to the right.

and other_anchor common types are:

  • NSLayoutYAxisAnchor
  • NSLayoutXAxisAnchor
  • AnonymousRelation

the AnonymousRelation is an enumeration that contains:

  • parent which are where the same anchor of parent UIView
  • safeArea which are where the same anchor of parent safe area UIView
  • myself which are where the same anchor of current UIView
  • mySafeArea which are where the same anchor of current safe area UIView
  • previous which are where the same anchor of previous planned UIView
  • previousSafeArea which are where the same anchor of previous planned UIView
  • keyboard which are the same anchor of parent keyboard guide of UIView
  • keyboardSafeArea which are the same anchor of parent keyboard guide of UIView intersect with safe area

the keyboard and keyboardSafeArea are all powered by Clavier

You could also pass UILayoutPriority if you need. If you ignore it, it will be lesser than the previous priority started by mandatory.

So lets say you want your view to fill the bottom of its superview, you could just use AnonymousRelation instead of declare explicitly:

myView.plan
    .right(.equal, to: .parent)
    .bottom(.equal, to: .parent)
    .left(.equal, to: .parent)

Related Anchor

If your AnonymousRelation anchor is different than your anchor, you could use RelatedAnchor<NSLayoutXAxisAnchor>. It's actually the anchor extractor from AnonymousRelation.

So lets say you want to make your view always on top of keyboard, just do this:

myView.plan
    .bottom(.equal, to: .top(of: .keyboard))
    .right(.equalTo(8), to: .safeArea)
    .left(.equalTo(8), to: .safeArea)

so instead of calling the previous anchor explicitly, you call them like that.

Positioning Shortcut

There are some shortcuts for Positioning. For center anchor:

  • func center(_ relation: LayoutRelation<CoordinateOffsets>, to view: UIView, priority: UILayoutPriority? = nil) -> Self
  • func center(_ relation: LayoutRelation<CoordinateOffsets>, to anchor: AnonymousRelation, priority: UILayoutPriority? = nil) -> Self

the CoordinateOffsets is struct which contains xOffset and yOffset. The center is shortcut to centerX and centerY, it will automatically assign NSLayoutConstraints relation to same center anchor to the UIView or AnonymousRelation. Example:

myView.plan
    .center(.equalTo(.init(xOffset: 8, yOffset: 8)), to: .parent)

For vertical and horizontal position:

  • func vertical(_ relation: LayoutRelation<InsetsConvertible>, to anchor: AnonymousRelation, priority: UILayoutPriority? = nil) -> Self
  • func vertical(_ relation: LayoutRelation<InsetsConvertible>, to view: UIView, priority: UILayoutPriority? = nil) -> Self
  • func horizontal(_ relation: LayoutRelation<InsetsConvertible>, to view: UIView, priority: UILayoutPriority? = nil) -> Self
  • func horizontal(_ relation: LayoutRelation<InsetsConvertible>, to anchor: AnonymousRelation, priority: UILayoutPriority? = nil) -> Self

the InsetsConvertible can be UIEdgeInsets, UIVerticalInsets, UIHorizontalInsets, CGFloat, Int or Double. Single types like CGFloat, Int or Double will be treated as UIEdgeInsets with same insets for top, left, right and bottom. The vertical will be assign top and bottom anchor to UIView or AnonymousRelation same anchors. Example:

myView.plan 
    .vertical(equalTo(16), to: .parent)

For all edges:

  • func edges(_ relation: LayoutRelation<InsetsConvertible>, to anchor: AnonymousRelation, priority: UILayoutPriority? = nil) -> Self

It will automatically assign top, left, bottom and right anchor to UIView or AnonymousRelation same anchors. Example:

myView.plan 
    .edges(.equalTo(16), to: .parent)

For any specific position:

  • func at(_ positions: [LayoutEdge], _ relation: LayoutRelation<InsetsConvertible>, to anchor: AnonymousRelation, priority: UILayoutPriority? = nil) -> Self
  • func at(_ viewRelation: RelatedPosition, _ relation: LayoutRelation<InsetsConvertible>, priority: UILayoutPriority? = nil) -> Self
  • func inBetween(of view: UIView, and otherView: UIView, _ position: MiddlePosition, priority: UILayoutPriority? = nil) -> Self

Array of LayoutEdge have static var extensions which are:

  • topLeft which is [.top, .left]
  • topRight which is [.top, .right]
  • bottomLeft which is [.bottom, .left]
  • bottomRight which is [.bottom, .right]
  • fullLeft which is [.left, .top, .bottom]
  • fullRight which is [.right, .top, .bottom]
  • fullBottom which is [.bottom, .left, .right]
  • fullTop which is [.top, .left, .right]
  • edge which is [.top, .bottom, .left, .right]

RelatedPosition is enumeration which are:

  • topOf(UIView) which will mark bottom to be at top of other UIView
  • bottomOf(UIView) which will mark top to be at bottom of other UIView
  • leftOf(UIView) which will mark right to be at left of other UIView
  • rightOf(UIView) which will mark left to be at right of other UIView
  • topOfAndParallelWith(UIView) which same as topOf, but with same left and right as other UIView
  • bottomOfAndParallelWith(UIView) which same as bottomOf, but with same left and right as other UIView
  • leftOfAndParallelWith(UIView) which same as leftOf, but with same top and bottom as other UIView
  • rightOfAndParallelWith(UIView) which same as rightOf, but with same top and bottom as other UIView

MiddlePosition is enumeration with LayoutRelation<InsetsConvertible> parameter:

  • horizontally(LayoutRelation)
  • vertically(LayoutRelation)

Example:

myView.plan 
    .at(.topLeft, .equal, to: .parent)
    .at(.rightOf(otherView), .equalTo(8))
otherView.plan
    .inBetween(of: myView, and: anyView, .horizontally(.equalTo(8)))

View Dimension

Basic Dimensioning

Create dimension constraints for a View is easy. You just need to declare which anchor should have relation to others:

myView.plan
    .height(.equalTo(otherView.heightAnchor), multiplyBy: 2)
    .width(.equalTo(.parent), .width)
    .height(.lessThanTo(100))
    .width(.moreThanTo(100))

The anatomy of dimension plan is:

func dimension_name(_ _relation: InterRelation of AnonymousRelation, __ dimension: LayoutDimension, multiplyBy multiplier: CGFloat, constant: CGFloat, priority: UILayoutPriority? = nil) -> Planner

this one could be used to plan any dimension constraints with any AnonymousRelation dimension anchor. The other dimension plan is:

func dimension_name(_ relation: InterRelation of NSLayoutDimension, multiplyBy multiplier: CGFloat, constant: CGFloat, priority: UILayoutPriority? = nil) -> Planner

this one could be used to plan any dimension constraints with any NSLayoutDimension anchor. The last dimension plan is:

func dimension_name(_ relation: InterRelation of CGFloat, priority: UILayoutPriority? = nil) -> Planner

this one could be used to plan any dimension constraints against any constant.

The InterRelation is the enumeration with generic parameter:

  • moreThanTo(Related)
  • lessThanTo(Related)
  • equalTo(Related)

and LayoutDimension is enumeration of dimension:

  • height
  • width

a multiplier is a value that will be multiplied by dimension anchor. The constant will be added to the dimension.

Dimensioning Shortcut

There are some shortcut for Dimensioning which are:

  • func size(_ relation: InterRelation<CGSize>, priority: UILayoutPriority? = nil) -> Self
  • func size(_ relation: InterRelation<UIView>, multiplyBy multiplier: CGFloat = 1, constant: CGFloat = 0, priority: UILayoutPriority? = nil) -> Self

both will automatically assign both width and height constraints towards CGSize constant or UIView size. Example:

myView.plan
    .size(.equalTo(otherView), multiplyBy: 2)
otherView.plan 
    .size(.equalTo(.init(width: 24, height: 24)))

Draftsman Planned

Draftsman Planned protocol is the protocol that makes any UIView or UIViewController can have its predefined view plan and applied it using applyPlan method. The protocol is declared like this:

public protocol Planned {
    @LayoutPlan
    var viewPlan: ViewPlan { get }
    
    func applyPlan()
}

The only thing you need to implement is the viewPlan getter:

import Draftsman

class MyViewController: UIViewController, Planned {
    
    @LayoutPlan
    var viewPlan: ViewPlan {
        UIStackView(axis: .vertical, spacing: 32).plan
            .center(.equal, to: .parent)
            .horizontal(.equalTo(16), to: .safeArea)
            .vertical(.moreThanTo(16), to: .safeArea)
            .insertStacked {
                MyView()
                MyOtherView()
                SomeOtherView()
            }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        applyPlan()
    }
}

Every time you call applyPlan, it will always remove all subviews from UIView root and recreate it


Fragment

Fragment in Draftsman is actually an ordinary UIView (or TableViewCell/CollectionViewCell) that can do a plan for itself and its subviews by using Planned capabilities

public protocol Fragment: Planned {
    func fragmentWillPlanContent()
    func fragmentDidPlanContent()
}
  • fragmentWillPlanContent() will be called before planContent(_:) is called. its optional
  • fragmentDidPlanContent() will be called after planContent(_:) is called. its optional

The mechanism is very straightforward. lets say you have this simple fragment:

class MySimpleFragment: UIView, Fragment {
    var margin = UIEdgeInsets(insets: 8)
    
    @LayoutPlan
    var viewPlan: ViewPlan {
        UIButton().plan.edges(equalTo(margin), to: .parent)
    }
}

to put it in ViewController bottom edges:

class MySimpleViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        planContent {
            MySimpleFragment().plan
                .at(.fullBottom, .equal, to: .parent)
        }
}

what happening here is when you fit simpleFragment into view, it will call simpleFragment's planContent(_:) where the InsertablePlan is the simpleFragment's LayoutPlaner. If it's described as hierarchal pseudocode and where the part is executed. It will be looked like this:

  • viewDidLoad
  • planContent
  • fit simpleFragment inside view
  • add simpleFragment as subviews of view controller view
  • create LayoutPlaner with simpleFragment as its view
  • call simpleFragment's fragmentWillPlanContent() method which not implemented so its doing nothing
  • get simpleFragment's viewPlan and create its constraints and fit all its subviews
  • fit UIButton inside simpleFragment
  • create edges contraints
  • call simpleFragment's fragmentDidPlanContent() method which not implemented so its doing nothing
  • create fullBottom constraints
  • activate all constraints created inside

There are two other extensions method you could use to call planContent indirectly:

  • func planFragment(delegate: PlanDelegate? = nil) which will be call fragmentWillPlanContent() and activate all created constraints right away and fragmentDidPlanContent()
  • func replanContent(delegate: PlanDelegate? = nil) which will remove all its and subviews constraints created by Draftsman and call planFragment(delegate:)

Fragment View

There is UIView that already implemented Fragment that you can extend named FragmentView. It has some more open method that you can use:

  • func fragmentWillLayoutForTheFirstTime() which will be called inside layoutSubviews() and only once at the first time before super.layoutSubviews()
  • func fragmentDidLayoutForTheFirstTime() which will be called inside layoutSubviews() and only once at the first time after super.layoutSubviews()

The advantages of the FragmentView are:

Capabilities Fragment View Implementing Own Fragment
have fragmentWillLayoutForTheFirstTime() YES NO
have fragmentDidLayoutForTheFirstTime() YES NO
automatically run planFragment when inserted to any UIView YES NO
automatically run planFragment when planned using Draftsman YES YES

example:

class MySimpleFragment: UIView, Fragment {
    var margin = UIEdgeInsets(insets: 8)
    
    @LayoutPlan
    var viewPlan: ViewPlan {
        UIButton().plan.edges(equalTo(margin), to: .parent)
    }
    
    func fragmentDidLayoutForTheFirstTime() {
        addGradient(with: self.bounds)
    }
}

Fragment Cell

There is Fragment created specifically for cell which named FragmentCell:

public protocol FragmentCell: Fragment {
    var layoutPhase: CellLayoutingPhase { get }
    var planningBehavior: CellPlanningBehavior { get }
    func planningOption(on phase: CellLayoutingPhase) -> PlanningOption
}

You're not supposed to implement FragmentCell by yourself but by extend TableFragmentCell which is UITableViewCell that implement FragmentCell or CollectionFragmentCell which is UICollectionViewCell that implement FragmentCell. The reason is that all of the FragmentCell implementation is implemented there as part of how those FragmentCell should behave.

Behavior and Phase

As we could see before, the FragmentCell have two properties and one added method:

  • var layoutPhase: CellLayoutingPhase { get }
  • var planningBehavior: CellPlanningBehavior { get }
  • func planningOption(on phase: CellLayoutingPhase) -> PlanningOption

The layoutPhase is the phase of the Cell, which is an enumeration:

  • firstLoad which indicated that the Cell is just created
  • setNeedsLayout which indicated that the Cell setNeedsLayout() is just called
  • reused which indicated that the Cell is being reused
  • **none

the planningBehavior is the behavior of the cell during layouting which could be overridden if needed. It is enumeration which contains:

  • planOnce which will only call planContent(_:) during firstLoad phase
  • planOn(CellLayoutingPhase) which will only call planContent(_:) during the given CellLayoutingPhase
  • planOnEach([CellLayoutingPhase]) which will only call planContent(_:) during each given CellLayoutingPhase
  • planIfPossible which will always call planContent(_:) for any CellLayoutingPhase

the planningOption(on:) will be called before planContent(_:) is called by any phase. It will asked what PlanningOption you want to use when call planContent(_:). The default is append when firstLoad phase and starFresh on the other phases. You could read more about PlanningOption here. Example:

class EventCollectionCell: CollectionFragmentCell {
    override var planningBehavior: CellPlanningBehavior { .planIfPossible }

    lazy var imageView: UIImageView = .init()
    
    @LayoutPlan
    var viewPlan: ViewPlan {
        imageView.plan.edges(equalTo(margin), to: .parent)
    }

    override func planningOption(on phase: CellLayoutingPhase) -> PlanningOption {
        switch phase {
        case .firstLoad:
            return .append
        default:
            return .renew
        }
    }
}

Cell at the example above will always create viewPlan at any phases, but will only do append on firstLoad and the rest will be renew the current constraints.

If you want to manually create viewPlan during any phases, you could just call layoutContentIfNeeded() it will layout content if the current phase is whitelisted in planningBehavior and return Bool indicated that the planContent(_:) is called or not.

Table Fragment Cell

TableFragmentCell is the UITableViewCell that implement FragmentCell. Other than what UITableViewCell, Fragment and FragmentCell feature, it have one method that could help you determine cell dimension:

func calculatedCellHeight(for cellWidth: CGFloat) -> CGFloat

cellWidth is the width of the cell which already calculated according to the tableView content width and insets. The default return value is automatic, but it could be came in handy if the cell NSLayoutConstraints alone cannot give the exact dimension of the cell. Example:

class MyCell: TableFragmentCell {
    lazy var collectionLayout: UICollectionViewFlowLayout = .init()
    lazy var collectionView: UICollectionView = .init(frame: .zero, collectionViewLayout: collectionLayout)
    
    @LayoutPlan
    var viewPlan: ViewPlan {
        collectionView.plan.edges(equalTo(margin), to: .parent)
    }
    
    override func fragmentWillPlanContent() {
        collectionView.allowsSelection = true
        collectionView.backgroundColor = .clear
        collectionView.allowsSelection = true
        collectionLayout.scrollDirection = .horizontal
        collectionLayout.itemSize = .init(width: .x64, height: .x48)
        collectionLayout.minimumInteritemSpacing = .zero
        collectionLayout.minimumLineSpacing = .zero
    }
    
    override func calculatedCellHeight(for cellWidth: CGFloat) -> CGFloat {
        128
    }
}

If you want to inject cellSize calculator, just pass a closure to whenNeedCellSize method at the cell

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableFragmentCell
    cell.whenNeedCellSize { width in
        return 128
    }
    return cell
}

The cell above will have a height of 128 regarding how long is cellWidth.

Collection Fragment Cell

CollectionFragmentCell is the UICollectionViewCell that implement FragmentCell. Other than what UICollectionViewCell, Fragment and FragmentCell feature.

class EventCollectionCell: CollectionFragmentCell {
    lazy var imageView: UIImageView = .init()
    
    @LayoutPlan
    var viewPlan: ViewPlan {
        imageView.plan.edges(equalTo(margin), to: .parent)
    }
}

Contribute

You know how, just clone and do pull request

You might also like...
AppStoreClone - Understanding the complex layout of app store using UICompositional layout in swift
AppStoreClone - Understanding the complex layout of app store using UICompositional layout in swift

AppStoreClone Understanding the complex layout of app store using UICompositiona

A custom layout built on top of SwiftUI's Layout API that lays elements out in multiple lines. Similar to flex-wrap in CSS, CollectionViewFlowLayout.
A custom layout built on top of SwiftUI's Layout API that lays elements out in multiple lines. Similar to flex-wrap in CSS, CollectionViewFlowLayout.

WrapLayout A custom layout built on top of SwiftUI's Layout API that lays elements out in multiple lines. Similar to flex-wrap in CSS, CollectionViewF

Write concise Autolayout code
Write concise Autolayout code

Winner of Hacking with Swift Recommended award You + Stevia = 🦄 💡 Write concise, readable layouts 🏖 Reduce your maintenance time 🎨 Compose your st

📱AutoLayout can be set differently for each device
📱AutoLayout can be set differently for each device

DeviceLayout DeviceLayout is a Swift framework that lets you set Auto Layout constraints's differently for each device Using only IBInspector of Xcode

An easier and faster way to code Autolayout
An easier and faster way to code Autolayout

EZAnchor 中文介绍 An easier way to code Autolayout Are you annoyed of coding .active = true while using Autolayout Anchors over and over again? Are you an

MisterFusion is Swift DSL for AutoLayout. It is the extremely clear, but concise syntax, in addition, can be used in both Swift and Objective-C. Support Safe Area and Size Class.
MisterFusion is Swift DSL for AutoLayout. It is the extremely clear, but concise syntax, in addition, can be used in both Swift and Objective-C. Support Safe Area and Size Class.

MisterFusion MisterFusion makes more easier to use AutoLayout in Swift & Objective-C code. Features Simple And Concise Syntax Use in Swift and Objecti

The fast path to autolayout views in code
The fast path to autolayout views in code

NorthLayout The fast path to autolayout views in code Talks https://speakerdeck.com/banjun/auto-layout-with-an-extended-visual-format-language at AltC

A Swift Autolayout DSL for iOS & OS X
A Swift Autolayout DSL for iOS & OS X

SnapKit is a DSL to make Auto Layout easy on both iOS and OS X. ⚠️ To use with Swift 4.x please ensure you are using = 4.0.0 ⚠️ ⚠️ To use with Swift

This
This "Calculator" application is a simple one screen design of calculator screen i have made this single screen design application just to practice AutoLayout concepts.

Calculator Layout This "Calculator" application is a simple one screen design of calculator screen i have made this single screen design application j

Comments
  • Remove test dependency from distribution

    Remove test dependency from distribution

    Since some projects will have different Quick and Nimble versions, and it shouldn't include in the distribution, it is now removed from dependency in Swift Package Manager version.

    opened by hainayanda 0
  • Minor Improvement

    Minor Improvement

    • Make backToRoot in ConstraintBuilderRootRecoverable public for more easier extension
    • Make validUniques check from reversed so it will first use the last similar constraint found
    • Add Axis Constraint test
    opened by hainayanda 0
  • Minor improvement

    Minor improvement

    • Add missing identifier transfer when same planned constraints is already there
    • Add viewController and context transfer from ViewPlanBuilder
    • Add More Unit test
    opened by hainayanda 0
  • Performance Improvement

    Performance Improvement

    • Ignore any Planned component (Fragment included) if it's already planned, thus improving rendering speed when the view contains many fragments that have already been applied
    • Will throw fatalError if any Planned component try to insert child view that is Planned component (Fragment included)
    • Refactor protocol and class hierarchy to hide some unnecessary public implementation
    • Update platform to iOS 12
    • Update swift to swift 5
    • Remove deprecated code
    opened by hainayanda 0
Releases(3.0.7)
Owner
Nayanda Haberty
Programmer. What else?
Nayanda Haberty
🏗 MondrianLayout - describing structured layout for AutoLayout

?? A DSL based layout builder for AutoLayout

Muukii 155 Dec 10, 2022
Harness the power of AutoLayout NSLayoutConstraints with a simplified, chainable and expressive syntax. Supports iOS and OSX Auto Layout

Masonry Masonry is still actively maintained, we are committed to fixing bugs and merging good quality PRs from the wider community. However if you're

null 18k Jan 5, 2023
A declarative Auto Layout DSL for Swift :iphone::triangular_ruler:

Cartography ?? ?? Using Cartography, you can set up your Auto Layout constraints in declarative code and without any stringly typing! In short, it all

Robb Böhnke 7.3k Jan 4, 2023
A declarative UIKit for improve layout productivity when developing an iOS application

TifoKit A declarative UIKit for improve layout productivity when developing an iOS application Requirements Min. iOS 11 Swift 5+ Installation Currentl

Tifo Audi A.P 22 Aug 9, 2022
⚓️ Declarative, extensible, powerful Auto Layout

EasyAnchor ❤️ Support my apps ❤️ Push Hero - pure Swift native macOS application to test push notifications PastePal - Pasteboard, note and shortcut m

Khoa 449 Nov 10, 2022
Lightweight declarative auto-layout framework for Swift

SwiftyLayout SwiftyLayout is a framework that allows to describe layout constraints (ie NSLayoutConstraint) as a simple mathematical formula in a Swif

Hisakuni Fujimoto 15 Nov 7, 2017
Declarative Auto Layout in Swift, clean and simple

Tails Tails is a take on declarative Auto Layout. If you don't like typing (like me), it might be your kind of thing! Tails is written in Swift and cu

Nick Tymchenko 17 Jan 31, 2019
LayoutLoopHunter - Runtime-based setup for tracking autolayout feedback loops

LayoutLoopHunter The library helps to catch the OOMs caused by Autolayout Feedback Loop by replicating the behavior of UIViewLayoutFeedbackLoopDebuggi

Ruslan Serebriakov 108 Oct 1, 2022
Auto Layout (and manual layout) in one line.

Auto Layout (and manual layout) in one line. Quick Look view.bb.centerX().below(view2).size(100) It’s equivalent to iOS 9 API: view.centerXAnchor.cons

Javier Zhang 74 Oct 19, 2022
Auto Layout made easy with the Custom Layout.

Auto Layout made easy with the Custom Layout. Getting started CocoaPods CocoaPods is a dependency manager for Cocoa projects. You can install it with

Malith Nadeeshan 1 Jan 16, 2022