VATextureKitSpec 2.1.0

VATextureKitSpec 2.1.0

VAndrJ 维护。



  • 作者:
  • Volodymyr Andriienko

VATextureKit

Texture 库的包装器,添加了一些功能。

安装

CocoaPods

将以下内容添加到 Podfile 中

pod 'VATextureKitRx'    // includes additional wrappers.
or
pod 'VATextureKit'      // includes Texture nodes wrappers.
or
pod 'VATextureKitSpec'  // includes only Layout Spec wrappers.

在终端中的项目目录中

pod install

或者只需尝试示例项目

pod try 'VATextureKit'

最低部署目标:iOS 11

布局规范

以下 LayoutSpec DSL 组件可以用于组合简单或非常复杂的布局。

VATextureKit Texture
ASStackLayoutSpec(垂直)
ASStackLayoutSpec(水平)
堆叠
安全区域 ASInsetLayoutSpec(带有安全区域内边距)
.填充 ASInsetLayoutSpec
.包裹 ASWrapperLayoutSpec
.角 ASCornerLayoutSpec
.安全 ASInsetLayoutSpec(带有安全区域内边距)
.居中 ASCenterLayoutSpec
.比例 ASRatioLayoutSpec
.覆盖 ASOverlayLayoutSpec
.背景 ASBackgroundLayoutSpec
.相对 ASRelativeLayoutSpec
.绝对 ASAbsoluteLayoutSpec

使用 ASStackLayoutSpec

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

使用 Column

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

示例

Column example

使用 ASStackLayoutSpec

ASStackLayoutSpec(
    direction: .horizontal,
    spacing: 4,
    justifyContent: .spaceBetween,
    alignItems: .start,
    children: [
        firstRectangleNode,
        secondRectangleNode,
    ]
)

使用 Row

Row(spacing: 4, main: .spaceBetween) {
    firstRectangleNode
    secondRectangleNode
}

示例

Column example

堆叠

堆叠:

Stack {
    firstRectangleNode
    secondRectangleNode
}

示例

Column example

安全区域

使用 ASStackLayoutSpecASDisplayNode 中,当 automaticallyRelayoutOnSafeAreaChanges = true

ASInsetLayoutSpec(
    insets: UIEdgeInsets(
        top: safeAreaInsets.top,
        left: safeAreaInsets.left,
        bottom: safeAreaInsets.bottom,
        right: safeAreaInsets.right
    ),
    child: ...
)

使用 SafeArea

SafeArea {
    ...
}
.填充

使用 ASInsetLayoutSpec

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

使用 .padding

titleTextNode
    .padding(.all(8))
.包裹

使用 ASWrapperLayoutSpec

ASWrapperLayoutSpec(layoutElement: imageNode)

使用 .wrapped

imageNode
    .wrapped()
.角

使用 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
}

使用 .corner

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

使用 ASStackLayoutSpecASDisplayNode 中,当 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
    )
}

使用 .safe

listNode
    .safe(in: self)
.居中

使用 ASCenterLayoutSpec

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

使用 .centered

buttonNode
    .centered()
.比例

使用 ASRatioLayoutSpec

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

使用 .ratio

imageNode
    .ratio(2 / 3)
.覆盖

使用 ASOverlayLayoutSpec

ASOverlayLayoutSpec(
    child: imageNode,
    overlay: gradientNode
)

使用 .overlay

imageNode
    .overlay(gradientNode)
.背景

使用 ASOverlayLayoutSpec

ASBackgroundLayoutSpec(
    child: gradientNode,
    background: imageNode
)

使用 .background

imageNode
    .background(gradientNode)
.相对

使用 ASOverlayLayoutSpec

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

使用 .relatively

buttonNode
    .relatively(horizontal: .start, vertical: .end)
.绝对

使用 ASAbsoluteLayoutSpec

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

使用 .absolutely

buttonNode
    .absolutely(frame: .frame, sizing: .sizeToFit)
更复杂的布局示例

Cell layout

使用 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))
}

使用原始 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,
                    ]
                ),
            ]
        )
    )
}

修饰符

.sized

设置 Node 的大小。

使用 style

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

使用 .sized

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

设置 Node 的 flex。

使用 style

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

使用 .flex

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

设置 Node 可用最大大小。

使用 style

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

使用 .maxConstrained

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

设置 Node 可用最小大小。

使用 style

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

使用 .minConstrained

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

节点

VADisplayNode

ASDisplayNode 的子类,自动管理子节点和处理主题更新。

VATextNode

ASTextNode 的子类,处理内容大小和主题更新。具有默认文本样式。

VAButtonNode

ASButtonNode 的子类,带有 onTap 闭包。

VACellNode

ASCellNode 的子类,自动管理子节点并处理主题更新。

VAImageNode

ASImageNode 的子类,具有参数化初始化器。

VASpacerNode

一个 ASDisplayNode 子类,用于填充 RowColumn 中的空间。

VASafeAreaDisplayNode

一个自动在安全区更改时重新布局的 VADisplayNode 子类。

VABaseGradientNode

一个带有 CAGradientLayer 根层的 ASDisplayNode 子类。

VALinearGradientNode

一个使用参数化初始化器来简化线性渐变创建的 VABaseGradientNode 子类。

VARadialGradientNode

一个使用参数化初始化器来简化径向渐变创建的 VABaseGradientNode 子类。

VAShapeNode

一个带有 CAShapeLayer 根层的 ASDisplayNode 子类。

VAEmitterNode

一个带有 CAEmitterLayer 支持的 ASDisplayNode 子类。

VATypingTextNode

一个带有打字动画的 VATextNode 子类。

示例

Typing example

VAReadMoreTextNode

一个具有“阅读更多”截断的简便方式的 VATextNode 子类。

代码

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

示例

Read more example

VACountingTextNode

一个带有计数初始化的 VATextNode 子类。

代码

countingTextNode.updateCount(to: Int.random(in: 0...1000))

示例

Link text node

VAShimmerNode

一个带有闪烁动画的 VADisplayNode 子类。

示例

Shimmer example

容器

VAListNode

*是 VATextureKitRx 的一部分

一个用于声明式使用的 ASCollectionNode 子类。

示例

List example

Dynamic heights layout example

Spec layout example

VATableListNode

*是 VATextureKitRx 的一部分

一个用于声明式使用的 ASTableNode 子类。

VAPagerNode

*是 VATextureKitRx 的一部分

一个用于声明式使用的 ASPagerNode 子类。一些可帮助模仿循环滚动的手法。

示例

Pager node example

VAViewController

处理主题更新的 ASDKViewController 的子类。

VANavigationController

处理主题更新和内容大小变化的 ASDKNavigationController 的子类。

VATabBarController

处理主题更新的 ASTabBarController 的子类。

VAWindow

处理主题更新和内容大小变化的 VAWindow 的子类。提供应用程序上下文。

VAContainerCellNode

用于将任何节点包装为 Cell Node。

包装器

VAViewWrapperNode

用于使用节点与 UIView 的容器。

VANodeWrapperView

用于使用视图与节点的容器。

VASizedViewWrapperNode

用于使用节点和继承其大小的 UIView 的容器。

动画

布局过渡动画

以简单的方式实现布局过渡动画。只需编写

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

示例

Layout transition example

节点动画

以简单的方式实现节点动画。

示例

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

结果

Layout transition example

更多示例

Layout transition example

主题

支持简单的方式应用主题。默认浅色/深色或自定义初始化。

扩展

ASDimension

初始化支持。

使用原始 Texture

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

使用 VATextureKit

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

数学

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

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

初始化器

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

变量

/// (top, left)
origin: CGPoint 

/// top + bottom
vertical: CGFloat

/// left + right
horizontal: CGFloat

初始化器

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)

预览

使用支持函数以简单方式实现节点预览。

sRepresentation(layout:)

Preview example

属性包装器

*是 VATextureKitRx 的一部分

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

使用这些包装器可以使代码更加简洁。

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

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

变为

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

private let someRelay = PublishRelay<String>()

变为

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

实验

VASlidingTabBarNode

Sliding tab bar

VAEmitterNode

Confetti emitter Text emitter

VALinkTextNode

Link text node