VATextureKit 2.0.2

VATextureKit 2.0.2

Maintained by VAndrJ.



 
Depends on:
Texture~> 3.2.0
VATextureKitSpec= 2.0.2
 

  • By
  • Volodymyr Andriienko

VATextureKit

Texture library wrapper with some additions.

Installation

CocoaPods

Add the following to your Podfile:

pod 'VATextureKit'

In the project directory in the Terminal:

pod install

Or just try the example project:

pod try 'VATextureKit'

Layout Specs

The following LayoutSpec DSL components can be used to compose simple or very complex layouts.

VATextureKit Texture
Column ASStackLayoutSpec (vertical)
Row ASStackLayoutSpec (horizontal)
Stack
SafeArea ASInsetLayoutSpec (with safeAreaInsets)
.padding ASInsetLayoutSpec
.wrapped ASWrapperLayoutSpec
.corner ASCornerLayoutSpec
.safe ASInsetLayoutSpec (with safeAreaInsets)
.centered ASCenterLayoutSpec
.ratio ASRatioLayoutSpec
.overlay ASOverlayLayoutSpec
.background ASBackgroundLayoutSpec
.relatively ASRelativeLayoutSpec
.absolutely ASAbsoluteLayoutSpec
Column

With ASStackLayoutSpec:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    ASStackLayoutSpec(
        direction: .vertical,
        spacing: 8,
        justifyContent: .start,
        alignItems: .start,
        children: [
            firstRectangleNode,
            secondRectangleNode,
        ]
    )
}

With Column:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    Column(spacing: 8) {
        firstRectangleNode
        secondRectangleNode
    }
}

Example:

Column example

Row

With ASStackLayoutSpec:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    ASStackLayoutSpec(
        direction: .horizontal,
        spacing: 4,
        justifyContent: .spaceBetween,
        alignItems: .start,
        children: [
            firstRectangleNode,
            secondRectangleNode,
        ]
    )
}

With Row:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    Row(spacing: 4, main: .spaceBetween) {
        firstRectangleNode
        secondRectangleNode
    }
}

Example:

Column example

Stack

Stack:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    Stack {
        firstRectangleNode
        secondRectangleNode
    }
}

Example:

Column example

SafeArea

With ASStackLayoutSpec in ASDisplayNode that automaticallyRelayoutOnSafeAreaChanges = true:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    ASInsetLayoutSpec(
        insets: UIEdgeInsets(
            top: safeAreaInsets.top,
            left: safeAreaInsets.left,
            bottom: safeAreaInsets.bottom,
            right: safeAreaInsets.right
        ),
        child: ...
    )
}

With SafeArea:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    SafeArea {
        ...
    }
}
.padding

With ASInsetLayoutSpec:

ASInsetLayoutSpec(
    insets: UIEdgeInsets(
        top: 8,
        left: 8,
        bottom: 8,
        right: 8
    ),
    child: titleTextNode
)

With .padding:

titleTextNode
    .padding(.all(8))
.wrapped

With ASWrapperLayoutSpec:

ASWrapperLayoutSpec(layoutElement: imageNode)

With .wrapped:

imageNode.wrapped()
.corner

With ASWrapperLayoutSpec:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    let spec = ASCornerLayoutSpec(
        child: imageNode,
        corner: badgeNode,
        location: .topRight
    )
    spec.offset = CGPoint(x: 4, y: 2)
    spec.wrapsCorner = false
    return spec
}

With .corner:

imageNode
    .corner(badgeNode, offset: CGPoint(x: 4, y: 2))
.safe

With ASStackLayoutSpec in ASDisplayNode that automaticallyRelayoutOnSafeAreaChanges = true:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    ASInsetLayoutSpec(
        insets: UIEdgeInsets(
            top: safeAreaInsets.top,
            left: safeAreaInsets.left,
            bottom: safeAreaInsets.bottom,
            right: safeAreaInsets.right
        ),
        child: listNode
    )
}

With .safe:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    listNode
        .safe(in: self)
}
.centered

With ASCenterLayoutSpec:

ASCenterLayoutSpec(
    centeringOptions: .XY,
    sizingOptions: .minimumXY,
    child: buttonNode
)

With .centered:

buttonNode
    .centered()
.ratio

With ASRatioLayoutSpec:

ASRatioLayoutSpec(
    ratio: 2 / 3,
    child: imageNode
)

With .ratio:

imageNode
    .ratio(2 / 3)
.overlay

With ASOverlayLayoutSpec:

ASOverlayLayoutSpec(
    child: imageNode,
    overlay: gradientNode
)

With .overlay:

imageNode
    .overlay(gradientNode)
.background

With ASOverlayLayoutSpec:

ASBackgroundLayoutSpec(
    child: gradientNode,
    background: imageNode
)

With .background:

imageNode
    .background(gradientNode)
.relatively

With ASOverlayLayoutSpec:

ASRelativeLayoutSpec(
    horizontalPosition: .start,
    verticalPosition: .end,
    sizingOption: .minimumSize,
    child: buttonNode
)

With .relatively:

buttonNode
    .relatively(horizontal: .start, vertical: .end)
.absolutely

With ASAbsoluteLayoutSpec:

buttonNode.style.preferredSize = frame.size
buttonNode.style.layoutPosition = frame.origin
return ASAbsoluteLayoutSpec(
    sizing: .sizeToFit,
    children: [buttonNode]
)

With .absolutely:

buttonNode
    .absolutely(frame: .frame, sizing: .sizeToFit)
More complex layout example

Cell layout

With VATextureKit:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    Column(cross: .stretch) {
        Row(main: .spaceBetween) {
            Row(spacing: 8, cross: .center) {
                testNameTextNode
                testInfoButtonNode
            }
            testStatusTextNode
        }
        titleTextNode
            .padding(.top(8))
        resultTextNode
            .padding(.top(32))
            .centered(.X)
        resultUnitsTextNode
            .centered(.X)
        referenceResultBarNode
            .padding(.vertical(24))
        Row(spacing: 16, cross: .center) {
            Column(spacing: 8) {
                Row(spacing: 8) {
                    resultBadgeImageNode
                    resultDescriptionTextNode
                }
                referenceValuesTextNode
            }
            accessoryImageNode
        }
    }
    .padding(.all(16))
}

With raw Texture:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    ASInsetLayoutSpec(
        insets: UIEdgeInsets(
            top: 16,
            left: 16,
            bottom: 16,
            right: 16
        ),
        child: ASStackLayoutSpec(
            direction: .vertical,
            spacing: 0,
            justifyContent: .start,
            alignItems: .stretch,
            children: [
                ASStackLayoutSpec(
                    direction: .horizontal,
                    spacing: 0,
                    justifyContent: .spaceBetween,
                    alignItems: .start,
                    children: [
                        ASStackLayoutSpec(
                            direction: .horizontal,
                            spacing: 8,
                            justifyContent: .start,
                            alignItems: .center,
                            children: [
                                testNameTextNode,
                                testInfoButtonNode,
                            ]
                        ),
                        testStatusTextNode,
                    ]
                ),
                ASInsetLayoutSpec(
                    insets: UIEdgeInsets(
                        top: 8,
                        left: 0,
                        bottom: 0,
                        right: 0
                    ),
                    child: titleTextNode
                ),
                ASCenterLayoutSpec(
                    centeringOptions: .X,
                    sizingOptions: .minimumXY,
                    child: ASInsetLayoutSpec(
                        insets: UIEdgeInsets(
                            top: 32,
                            left: 0,
                            bottom: 0,
                            right: 0
                        ),
                        child: resultTextNode
                    )
                ),
                ASCenterLayoutSpec(
                    centeringOptions: .X,
                    sizingOptions: .minimumXY,
                    child: resultUnitsTextNode
                ),
                ASInsetLayoutSpec(
                    insets: UIEdgeInsets(
                        top: 24,
                        left: 0,
                        bottom: 24,
                        right: 0
                    ),
                    child: referenceResultBarNode
                ),
                ASStackLayoutSpec(
                    direction: .horizontal,
                    spacing: 0,
                    justifyContent: .start,
                    alignItems: .center,
                    children: [
                        ASStackLayoutSpec(
                            direction: .vertical,
                            spacing: 8,
                            justifyContent: .start,
                            alignItems: .start,
                            children: [
                                ASStackLayoutSpec(
                                    direction: .horizontal,
                                    spacing: 8,
                                    justifyContent: .start,
                                    alignItems: .start,
                                    children: [
                                        resultBadgeImageNode,
                                        resultDescriptionTextNode,
                                    ]
                                ),
                                referenceValuesTextNode,
                            ]
                        ),
                        accessoryImageNode,
                    ]
                ),
            ]
        )
    )
}

Modifiers

.sized

Set Node's size.

With style:

imageNode.style.width = ASDimension(unit: .points, value: 320)
imageNode.style.height = ASDimension(unit: .points, value: 480)

With .sized:

imageNode
    .sized(width: 320, height: 480)
.flex

Set Node's flex.

With style:

titleTextNode.style.flexShrink = 0.1
titleTextNode.style.flexGrow = 1

With .flex:

titleTextNode
    .flex(shrink: 0.1, grow: 1)
.maxConstrained

Set Node's max possible size.

With style:

titleTextNode.style.maxWidth = ASDimension(unit: .points, value: 320)
titleTextNode.style.maxHeight = ASDimension(unit: .points, value: 100)

With .maxConstrained:

titleTextNode
    .maxConstrained(width: 320, height: 480)
.minConstrained

Set Node's min possible size.

With style:

titleTextNode.style.minWidth = ASDimension(unit: .points, value: 100)
titleTextNode.style.minHeight = ASDimension(unit: .points, value: 50)

With .minConstrained:

titleTextNode
    .minConstrained(width: 100, height: 50)

Nodes

VADisplayNode

A subclass of ASDisplayNode that automatically manages subnodes and handles theme updates.

VATextNode

A subclass of ASTextNode that handles content size and theme updates. Have default text styles.

VAButtonNode

A subclass of ASButtonNode with onTap closure.

VACellNode

A subclass of ASCellNode that automatically manages subnodes and handles theme updates.

VAImageNode

A subclass of ASImageNode with parametrized initializer.

VASpacerNode

A subclass of ASDisplayNode to fill space in Row / Column.

VASafeAreaDisplayNode

A subclass of VADisplayNode that automatically relayout on safe area changes.

VABaseGradientNode

A subclass of ASDisplayNode with CAGradientLayer root layer.

VALinearGradientNode

A subclass of VABaseGradientNode with parametrized initializer to simplify linear gradient creation.

VARadialGradientNode

A subclass of VABaseGradientNode with parametrized initializer to simplify radial gradient creation.

VATypingTextNode

A subclass of VATextNode with typing animation.

Example:

Typing example

VAReadMoreTextNode

A subclass of VATextNode with "Read more" truncation in easy way.

Code:

lazy var readMoreTextNode = VAReadMoreTextNode(
    text: .loremText,
    maximumNumberOfLines: 2,
    readMore: .init(
        text: "Read more",
        fontStyle: .headline,
        colorGetter: { $0.systemBlue }
    )
)

Example:

Read more example

VAShimmerNode

A subclass of VADisplayNode with shimmering animation.

Example:

Shimmer example

Containers

VAListNode

A subclass of ASCollectionNode to use it in declarative way.

Example:

List example

Dynamic heights layout example

Spec layout example

VATableListNode

A subclass of ASTableNode to use it in declarative way.

VAPagerNode

A subclass of ASPagerNode to use it in declarative way. Some crutches to mimic circular scrolling.

Example:

Pager node example

VAViewController

A subclass of ASDKViewController that handles theme updates.

VANavigationController

A subclass of ASDKNavigationController that handles theme updates and content size changes.

VATabBarController

A subclass of ASTabBarController that handles theme updates.

VAWindow

A subclass of VAWindow to handle theme updates and content size changes. Provides app context.

VAContainerCellNode

To wrap any node with Cell Node.

Wrappers

VAViewWrapperNode

Container to use UIView with nodes.

VANodeWrapperView

Container to use node with views.

VASizedViewWrapperNode

Container to use UIView with nodes and inheriting its size.

Animations

Layout transition animations

Layout transition animations in easy way. Just write:

override func animateLayoutTransition(_ context: ASContextTransitioning) {
    animateLayoutTransition(context: context)
}

Example:

Layout transition example

Node animations

Node animations in easy way.

Example:

pulseNode.animate(.scale(values: [1, 1.1, 0.9, 1.2, 0.8, 1.1, 0.9, 1]), duration: 1)

Result:

Layout transition example

More examples:

Layout transition example

Themes

Themes support in easy way. Default light / dark or custom init.

Property wrappers

  • Obs
    • Relay(value:) (BehaviorRelay)
    • Relay() (PublishRelay)

With these wrappers, the code becomes more concise.

BehaviorRelay
var someObs: Observable<String> { someRelay.asObservable() }

private let someRelay = BehaviorRelay<String>(value: "value")
...
someRelay.accept("value1")

becomes

@Obs.Relay(value: "value")
var someObs: Observable<String>
...
_someObs.rx.accept("value1")
PublishRelay
var someObs: Observable<String> { someRelay.asObservable() }

private let someRelay = PublishRelay<String>()

becomes

@Obs.Relay()
var someObs: Observable<String>

Extensions

ASDimension

Init support.

With raw Texture:

style.height = ASDimension(unit: .auto, value: 0)
style.height = ASDimension(unit: .points, value: height)
style.height = ASDimension(unit: .fraction, value: 0.3)

With VATextureKit:

style.height = .auto
style.height = .points(height)
style.height = .fraction(0.3)
style.height = .fraction(percent: 30)
CGSize

Math:

CGSize(width: 2, height: 2) * 2 = CGSize(width: 4, height: 4)

CGSize(width: 2, height: 2) + 1 = CGSize(width: 3, height: 3)

Initializer:

CGSize(same: 16) == CGSize(width: 16, height: 16)
UIEdgeInsets

Variables:

/// (top, left)
origin: CGPoint 

/// top + bottom
vertical: CGFloat

/// left + right
horizontal: CGFloat

Initializer:

UIEdgeInsets(all: 16) == UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)

UIEdgeInsets(vertical: 16) == UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0)

UIEdgeInsets(horizontal: 16) == UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
    
UIEdgeInsets(vertical: 4, horizontal: 8) == UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)

Previews

Node preview in easy way with support function:

sRepresentation(layout:)

Preview example

Experiments

VACountingTextNode

Link text node

VASlidingTabBarNode

Sliding tab bar

VALinkTextNode

Link text node