Skip to content
This repository has been archived by the owner on Mar 7, 2021. It is now read-only.

Latest commit

 

History

History
144 lines (102 loc) · 3.8 KB

basic_concepts.md

File metadata and controls

144 lines (102 loc) · 3.8 KB

Basic concepts

ALLKit has 3 core entities: layout node, layout spec and layout.

Layout node

Layout node is an atomic unit of UI - objective bridge between flexbox and UIKit.

There are 4 types of layout nodes:

standard sized custom sized
has view A C
has no view B D
  • A
LayoutNode(children: [/*...*/], {
    /* flexbox properties */
}) { (view: ViewType, isNew) in
    /* view properties */
}
  • B
LayoutNode(children: [/*...*/], {
    /* flexbox properties */
})
  • C
let sizeProvider: SizeProvider

LayoutNode(sizeProvider: sizeProvider, {
    /* flexbox properties */
}) { (view: ViewType, isNew) in
    /* view properties */
}
  • D
let sizeProvider: SizeProvider

LayoutNode(sizeProvider: sizeProvider, {
    /* flexbox properties */
})

In common case (A, B) to calculate layout you need to establish relationship between nodes (make node tree) and set up their flexbox properties. But sometimes (C, D) layout algorithm needs extra information about node size. There is a special protocol for this purpose - SizeProvider. Object that implements this protocol can calculate the size based on the width and height constraints. Typical example is text - in iOS you can calculate NSAttributedString size using boundingRect method. Nodes with size providers are always leafs.

Nodes with views (A, C) has trailing closure with generic view type (you can use UIView and its subclasses) and special optional to use parameter isNew, which is true if view is only created. Views passed to the closure always have correct frame. Note, that no default views are created for nodes without user-defined views (B, D).

Layout spec

If layout node is an atom, then layout spec is a molecule - group of atoms. Layout spec is a declarative UI component. Each component is a subclass of LayoutSpec or ModelLayoutSpec:

final class YourLayoutSpec: LayoutSpec {
    override func makeNodeWith(boundingDimensions: LayoutDimensions<CGFloat>) -> LayoutNodeConvertible {
        ...
    }
}
final class YourLayoutSpec: ModelLayoutSpec<SomeModel> {
    override func makeNodeWith(boundingDimensions: LayoutDimensions<CGFloat>) -> LayoutNodeConvertible {
        self.model...
    }
}

Layout specs have two important features:

  1. Encapsulation - UI building is separate from other logic.
  2. Composition - components can be easily combined:
struct Model1 {
    ...
}

struct Model2 {
    let submodel: Model1
    ...
}

final class LayoutSpec1: ModelLayoutSpec<Model1> {
    override func makeNodeWith(boundingDimensions: LayoutDimensions<CGFloat>) -> LayoutNodeConvertible {
        ...
    }
}

final class LayoutSpec2: ModelLayoutSpec<Model2> {
    override func makeNodeWith(boundingDimensions: LayoutDimensions<CGFloat>) -> LayoutNodeConvertible {
        let node1 = LayoutSpec1(model: model.submodel).makeNodeWith(boundingDimensions: boundingDimensions)

        let node2 = LayoutNode(children: [node1])

        ...
    }
}

Layout

Layout is a view stencil.

  1. Spec makes layout:
let layout = layoutSpec.makeLayoutWith(
    boundingDimensions: CGSize(...).layoutDimensions
)
  1. Layout makes view:
let view = layout.makeView() // convenience method

or

view.frame = CGRect(origin: .zero, size: layout.size)

layout.makeContentIn(view: view)

The default implementation of the Layout protocol creates subviews in the provided view (if no subviews), otherwise reuses existing subviews.

Methods makeContentIn and makeView must be used from main thread.

Example

Demo component