VATextureKitCombine 2.1.0

VATextureKitCombine 2.1.0

VAndrJ 维护。



  • 赞助商:
  • Volodymyr Andriienko

VATextureKit

StandWithUkraine Support Ukraine

版本平台许可证 VATextureKitSpec

版本平台许可证 VATextureKit

版本平台许可证 VATextureKitRx

版本平台许可证 VATextureKitCombine

Texture 库封装,增加了某些功能。

此外,也可以看看这个带有辅助宏的包:VATextureKitMacro

安装

CocoaPods

将以下内容添加到您的 Podfile 中

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

在终端中的项目目录

pod install

或尝试示例项目

pod try 'VATextureKit'

最低部署目标

1.9.xiOS 11

2.x.xiOS 14

布局规格

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

VATextureKit Texture
ASStackLayoutSpec (垂直)
ASStackLayoutSpec (水平)
堆叠
安全区域 ASInsetLayoutSpec(带有安全区域边距)
.padding ASInsetLayoutSpec
.wrapped ASWrapperLayoutSpec
.corner ASC نمایش‌گذارLayoutSpec
.safe ASInsetLayoutSpec(带有安全区域边距)
.centered ASCenterLayoutSpec
.ratio ASRatioLayoutSpec
.overlay ASOverlayLayoutSpec
.background ASBackgroundLayoutSpec
.relatively ASRelativeLayoutSpec
.absolutely 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

安全区域

在自动在安全区域更改时重新布局的 ASDisplayNode 中使用 ASStackLayoutSpec

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

使用 SafeArea

SafeArea {
    ...
}
.padding

使用 ASInsetLayoutSpec

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

使用 .padding

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

使用 ASWrapperLayoutSpec

ASWrapperLayoutSpec(layoutElement: imageNode)

使用 .wrapped

imageNode
    .wrapped()
.corner

使用 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))
.safe

在自动在安全区域更改时重新布局的 ASDisplayNode 中使用 ASStackLayoutSpec

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)
.centered

使用 ASCenterLayoutSpec

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

使用 .centered

buttonNode
    .centered()
.ratio

使用 ASRatioLayoutSpec

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

使用 .ratio

imageNode
    .ratio(2 / 3)
.overlay

使用 ASOverlayLayoutSpec

ASOverlayLayoutSpec(
    child: imageNode,
    overlay: gradientNode
)

使用 .overlay

imageNode
    .overlay(gradientNode)
.background

使用 ASOverlayLayoutSpec

ASBackgroundLayoutSpec(
    child: gradientNode,
    background: imageNode
)

使用 .background

imageNode
    .background(gradientNode)
.relatively

使用 ASOverlayLayoutSpec

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

使用 .relatively

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

使用 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 的灵活性。

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

带有 onTap 闭包的 ASButtonNode 的子类。

VACellNode

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

VAImageNode

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

VASpacerNode

用于填充 Row / Column 空间的 ASDisplayNode 的子类。

VASafeAreaDisplayNode

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

VABaseGradientNode

带有 CAGradientLayer 根层的 ASDisplayNode 的子类。

VALinearGradientNode

带有简化线性渐变创建的参数化初始化器的 VABaseGradientNode 的子类。

VARadialGradientNode

带有简化径向渐变创建的参数化初始化器的 VABaseGradientNode 的子类。

VAShapeNode

带有 CAShapeLayer 根层的 ASDisplayNode 的子类。

VAEmitterNode

带有 CAEmitterLayer 支持的 ASDisplayNode 的子类。

VATypingTextNode

带有输入动画的 的子类。

示例

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

VAVisualEffectNode

视觉效果节点。

示例代码

VAMaterialVisualEffectNode(
    style: .ultraThinMaterial,
    context: .init(
        corner: .init(radius: 24),
        border: .init(color: AppearanceColor(light: .cyan.withAlphaComponent(0.2), dark: .orange.withAlphaComponent(0.2)).wrappedValue),
        shadow: .init(radius: 24),
        neon: .init(color: AppearanceColor(light: .cyan, dark: .orange).wrappedValue, width: 2),
        pointer: .init(radius: 32, color: AppearanceColor(light: .cyan, dark: .orange).wrappedValue),
        excludedFilters: [.luminanceCurveMap, .colorSaturate, .colorBrightness]
    )
)

示例

Shimmer example

容器

VAComparisonNode

ASDisplayNode 的子类,用于滑动比较两个子节点。

示例

List 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

用于包装任何节点与单元格节点。

VASelfSizingListContainerNode

用于包装 ASCollectionNode 以在垂直或水平方向上启用自定大小行为。建议不要与大型列表一起使用此类。

包装器

VAViewWrapperNode

用于在节点中使用的 UIView

VANodeWrapperView

用于在视图中使用节点的容器。

VASizedViewWrapperNode

用于使用自动布局的 UIView 与节点继承其大小。

示例

VASizedViewWrapperNode(
    childGetter: { MyAwesomeView() },
    sizing: .viewWidth
)
.sized(height: 140)
VAViewWrapperNode

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

示例

VAViewWrapperNode(
    childGetter: { MyAwesomeView(frame: .init(width: 48, height: 24)) },
    sizing: .inheritedWidth
)
.sized(height: 140)
VAViewHostingNode

用于使用 SwiftUI View 与节点继承其大小的容器。

示例

VAViewHostingNode(
    body: {
        Text("Some text")
            .background(Color.green.opacity(0.1))
    },
    sizing: .viewSize
)

动画

布局过渡动画

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

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 的一部分

  • Obs
    • 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