VATextureKit
Texture library wrapper with some additions.
- Installation
- Layout Specs
- Modifiers
- Nodes
- Containers
- Wrappers
- Animations
- Themes
- Property wrappers
- Extensions
- Previews
- Experiments
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:
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:
Stack
Stack
:
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
Stack {
firstRectangleNode
secondRectangleNode
}
}
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
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.
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:
Containers
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:
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:
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:
More examples:
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:)