Describing AutoLayout with imaginable how it lays out programmatically - MondrianLayout

Updated
Jul 10, 2021 7:04 AM
Created
Jul 4, 2021 1:31 PM
Tags
Keywords
Date

Digression: SwiftUI never removes UIKit

Apple is working on creating SwiftUI dedicated. but it does not mean they're removing UIKit from our development.

SwiftUI is just an abstraction layer to describe UI, which depends on who renders it. In macOS, AppKit does. In iOS, UIKit does, In the home screen's widget, something special renderer create surface according to the representation of UI which described by SwiftUI interfaces.

Technically, SwiftUI does not have a function of rendering. It's just a representation. UIKit does instead, but actually, UIKit is also an abstraction layer of rendering. Highly optimized CoreAnimation and CoreGraphics do that using CPU and GPU.

Of course, Apple would update SwiftUI every year, we might be able to create more complex applications using only SwiftUI. Even if so they would still use UIKit inside. It's like creating highly customized components in javascript even if using React, we would use UIKit to do that and bridging with SwiftUI.

That's what I mean, UIKit is still important we would continue to use it at least partially.

Describing AutoLayout in code makes us hard to understand.

Using InterfaceBuilder has a lot of advantages and Not using InterfaceBuilder also has a lot, especially the case of using Swift.

If we create UI from only code, we may write the code in order to set the layout something like this.

let backgroundView = UIView.mock(backgroundColor: .neon(.violet))
let box1 = UIView.mock(backgroundColor: .neon(.red), preferredSize: .largeSquare)
let box2 = UIView.mock(backgroundColor: .neon(.yellow), preferredSize: .largeSquare)

view.addSubview(backgroundView)
view.addSubview(box1)
view.addSubview(box2)

backgroundView.translatesAutoresizingMaskIntoConstraints = false
box1.translatesAutoresizingMaskIntoConstraints = false
box2.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([

  backgroundView.topAnchor.constraint(equalTo: view.topAnchor),
  backgroundView.leftAnchor.constraint(equalTo: view.leftAnchor),
  backgroundView.rightAnchor.constraint(equalTo: view.rightAnchor),
  backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

  box1.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
  box1.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10),
  box1.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),

  box2.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
  box2.leftAnchor.constraint(equalTo: box1.rightAnchor, constant: 10),
  box2.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
  box2.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10)
])

Did you get to see what looks this code creates?

image

image

As you can see, describing AutoLayout constraints in code makes developer hard to understand. Additionally, modifying the layout is also hard. Imagine that if you need to swap those 2 boxes, you will write multiple lines again. You know what, updating layout often causes in business.

UIKit got UIStackView to solve this problem, you need to add another layer though.

MondrianLayout can describe layout which imaginable how it lays out in run time without using InterfaceBuilder

So how MondrianLayout changes it.

let backgroundView = UIView.mock(backgroundColor: .neon(.violet))
let box1 = UIView.mock(backgroundColor: .neon(.red), preferredSize: .largeSquare)
let box2 = UIView.mock(backgroundColor: .neon(.yellow), preferredSize: .largeSquare)

view.mondrian.buildSubviews {
  HStackBlock(spacing: 10) {
    box1
    box2
  }
  .padding(10)
  .background(backgroundView)
}

buildSubviews does followings:

  • Creates NSLayoutConstraint
  • Creates UILayoutGuide to lay out with stacking in line.
  • Adds views which used in the representation to the parent view.
    • Hierarchy also respects. backgroundView would be added behind the boxes.
  • Turns off translatesAutoresizingMaskIntoConstraints

We can swap those boxes by just like this. Changing the order of describing boxes.

HStackBlock(spacing: 10) {
  box2
  box1
}

image

But we still need to use classical style API

As explained above section, that uses Structured layout API to describe layout. However we might still need to use the style like using plain API to hold the flexibility of AutoLayout.

To support the case such as, MondrianLayout gives us the API which can describe AutoLayout constraints each condition. Which is like using the plain API but it's more fluently.

let backgroundView = UIView.mock(backgroundColor: .neon(.violet))
let box1 = UIView.mock(backgroundColor: .neon(.red), preferredSize: .largeSquare)
let box2 = UIView.mock(backgroundColor: .neon(.yellow), preferredSize: .largeSquare)

view.addSubview(backgroundView)
view.addSubview(box1)
view.addSubview(box2)

mondrianBatchLayout {

  backgroundView.mondrian.layout.edges(.toSuperview)

  box1.mondrian.layout
    .top(.toSuperview, 10)
    .left(.toSuperview, 10)
    .bottom(.toSuperview, -10)

  box2.mondrian.layout
    .top(.toSuperview, 10)
    .left(.to(box1).right, 10)
    .right(.toSuperview, -10)
    .bottom(.toSuperview, -10)

}

Wrapping up

MondrianLayout provides 2 ways to describe layout in code.

  1. Structured layout API - describing layout ergonomically.
  2. Classical style layout API - describing layout constraints fully controllable.

Those APIs can integrate each other, we can choose the best way to describe each case.

I hope you enjoy describing layout programmatically using MondrianLayout.