Add wrap container implementation

Добавил реализацию wrap в ContainerBlock, пока без снепшотов, отсматривал свои кейсы в семпл аппе.
This commit is contained in:
morevsavva
2022-09-05 17:41:20 +03:00
parent e9882c8863
commit d8650ed0fd
6 changed files with 107 additions and 4 deletions
@@ -30,6 +30,7 @@ public struct ContainerBlockLayout {
let children: [ContainerBlock.Child]
let gaps: [CGFloat]
let layoutDirection: ContainerBlock.LayoutDirection
let layoutMode: ContainerBlock.LayoutMode
let axialAlignment: Alignment
let size: CGSize
@@ -37,6 +38,7 @@ public struct ContainerBlockLayout {
children: [ContainerBlock.Child],
gaps: [CGFloat],
layoutDirection: ContainerBlock.LayoutDirection,
layoutMode: ContainerBlock.LayoutMode,
axialAlignment: Alignment,
size: CGSize
) {
@@ -44,12 +46,22 @@ public struct ContainerBlockLayout {
self.children = children
self.gaps = gaps
self.layoutDirection = layoutDirection
self.layoutMode = layoutMode
self.axialAlignment = axialAlignment
self.size = size
self.blockFrames = calculateBlockFrames()
}
private func calculateBlockFrames() -> [CGRect] {
switch layoutMode {
case .noWrap:
return calculateNoWrapLayoutFrames()
case .wrap:
return calculateWrapLayoutFrames()
}
}
private func calculateNoWrapLayoutFrames() -> [CGRect] {
var frames = [CGRect]()
let gapsSize = gaps.reduce(0, +)
var shift = CGPoint(x: 0, y: 0)
@@ -57,10 +69,11 @@ public struct ContainerBlockLayout {
case .horizontal:
let horizontallyResizableBlocks = children.map { $0.content }
.filter { $0.isHorizontallyResizable }
let widthOfHorizontallyNonResizableBlocks = widthsOfHorizontallyNonResizableBlocks.reduce(
0,
+
)
let widthOfHorizontallyNonResizableBlocks = widthsOfHorizontallyNonResizableBlocks
.reduce(
0,
+
)
let widthAvailableForResizableBlocks = (
size.width - widthOfHorizontallyNonResizableBlocks - gapsSize
)
@@ -134,6 +147,7 @@ public struct ContainerBlockLayout {
}
shift.y = axialAlignment.offset(forAvailableSpace: size.height, contentSize: y)
}
return frames.map {
let frame = $0.offset(by: shift).roundedToScreenScale
precondition(frame.isValidAndFinite)
@@ -141,6 +155,77 @@ public struct ContainerBlockLayout {
}
}
private func calculateWrapLayoutFrames() -> [CGRect] {
#if INTERNAL_BUILD
assert(gaps.allSatisfy(0), "You cannot use gaps in wrap container.")
assert(
!children
.contains { $0.content.isHorizontallyResizable || $0.content.isVerticallyResizable
},
"You cannot use resizable blocks in wrap container."
)
#endif
var frames = [CGRect]()
let buildingDirectionKeyPath: KeyPath<CGSize, CGFloat>
let transferDirectionKeyPath: KeyPath<CGSize, CGFloat>
switch layoutDirection {
case .horizontal:
buildingDirectionKeyPath = \.width
transferDirectionKeyPath = \.height
case .vertical:
buildingDirectionKeyPath = \.height
transferDirectionKeyPath = \.width
}
let groups: [[(child: ContainerBlock.Child, childSize: CGSize, lineOffset: CGFloat)]] =
children.reduce([[]]) { result, child in
let offset = (result.last?.last?.2 ?? 0) +
(result.last?.last?.1[keyPath: buildingDirectionKeyPath] ?? 0)
let childSize = child.content.size(forResizableBlockSize: .zero)
if offset + childSize[keyPath: buildingDirectionKeyPath] >
size[keyPath: buildingDirectionKeyPath] {
return result + [[(child, childSize, 0)]]
} else {
return (result.dropLast()) + [(result.last ?? []) + [(child, childSize, offset)]]
}
}
var currentLineOffset = 0.0
groups.forEach {
let groupHeight = $0.map(\.childSize).map(to: transferDirectionKeyPath).max() ?? 0
let contentLength = $0.map(\.childSize).map(to: buildingDirectionKeyPath).reduce(0, +)
$0.forEach {
let alignmentSpace = groupHeight - $0.childSize[keyPath: transferDirectionKeyPath]
let alignedLineOffset = currentLineOffset + $0.child.crossAlignment
.offset(forAvailableSpace: alignmentSpace)
let alignedElementOffset = $0.lineOffset.advanced(by: axialAlignment.offset(
forAvailableSpace: size[keyPath: buildingDirectionKeyPath],
contentSize: contentLength
).roundedToScreenScale)
switch layoutDirection {
case .horizontal:
frames
.append(CGRect(origin: CGPoint(
x: alignedElementOffset,
y: alignedLineOffset
), size: $0.childSize))
case .vertical:
frames
.append(CGRect(origin: CGPoint(
x: alignedLineOffset,
y: alignedElementOffset
), size: $0.childSize))
}
}
currentLineOffset += groupHeight
}
return frames
}
public private(set) var blockFrames: [CGRect] = []
public var leftInset: CGFloat {
@@ -18,6 +18,11 @@ public final class ContainerBlock: BlockWithLayout {
/// Child blocks are laid out vertically one after another
case vertical
}
public enum LayoutMode {
case wrap
case noWrap
}
public struct Child: Equatable {
public var content: Block
@@ -44,6 +49,7 @@ public final class ContainerBlock: BlockWithLayout {
}
public let layoutDirection: LayoutDirection
public let layoutMode: LayoutMode
public let widthTrait: LayoutTrait
public let heightTrait: LayoutTrait
public let axialAlignment: Alignment
@@ -59,6 +65,7 @@ public final class ContainerBlock: BlockWithLayout {
public init(
layoutDirection: LayoutDirection,
layoutMode: LayoutMode = .noWrap,
widthTrait: LayoutTrait = .resizable,
heightTrait: LayoutTrait = .intrinsic,
axialAlignment: Alignment = .leading,
@@ -80,6 +87,7 @@ public final class ContainerBlock: BlockWithLayout {
}
self.layoutDirection = layoutDirection
self.layoutMode = layoutMode
self.widthTrait = widthTrait
self.heightTrait = heightTrait
self.axialAlignment = axialAlignment
@@ -273,6 +281,7 @@ public final class ContainerBlock: BlockWithLayout {
children: children,
gaps: gaps,
layoutDirection: layoutDirection,
layoutMode: layoutMode,
axialAlignment: axialAlignment,
size: size
)
@@ -6,6 +6,7 @@ import CommonCore
extension ContainerBlock {
public func modifying(
layoutDirection: LayoutDirection? = nil,
layoutMode: LayoutMode? = nil,
widthTrait: LayoutTrait? = nil,
heightTrait: LayoutTrait? = nil,
axialAlignment: Alignment? = nil,
@@ -19,6 +20,7 @@ extension ContainerBlock {
) throws -> ContainerBlock {
try ContainerBlock(
layoutDirection: layoutDirection ?? self.layoutDirection,
layoutMode: layoutMode ?? self.layoutMode,
widthTrait: widthTrait ?? self.widthTrait,
heightTrait: heightTrait ?? self.heightTrait,
axialAlignment: axialAlignment ?? self.axialAlignment,
@@ -34,6 +36,7 @@ extension ContainerBlock {
public convenience init(
layoutDirection: LayoutDirection,
layoutMode: LayoutMode = .noWrap,
widthTrait: LayoutTrait = .resizable,
heightTrait: LayoutTrait = .intrinsic,
horizontalChildrenAlignment: Alignment = .leading,
@@ -64,6 +67,7 @@ extension ContainerBlock {
try self.init(
layoutDirection: layoutDirection,
layoutMode: layoutMode,
widthTrait: widthTrait,
heightTrait: heightTrait,
axialAlignment: axialAlignment,
@@ -9,6 +9,7 @@ extension ContainerBlock: ImageRenderableBlock {
children: children,
gaps: gaps,
layoutDirection: layoutDirection,
layoutMode: layoutMode,
axialAlignment: axialAlignment,
size: rect.size
).blockFrames
@@ -26,6 +26,7 @@ extension ContainerBlock {
children: children,
gaps: gaps,
layoutDirection: layoutDirection,
layoutMode: layoutMode,
axialAlignment: axialAlignment,
contentAnimation: contentAnimation,
anchorPoint: anchorPoint,
@@ -49,6 +50,7 @@ private final class ContainerBlockView: UIView, BlockViewProtocol, VisibleBounds
let children: [ContainerBlock.Child]
let gaps: [CGFloat]
let layoutDirection: ContainerBlock.LayoutDirection
let layoutMode: ContainerBlock.LayoutMode
let axialAlignment: Alignment
let contentAnimation: BlockAnimation?
let anchorPoint: AnchorPoint
@@ -131,6 +133,7 @@ private final class ContainerBlockView: UIView, BlockViewProtocol, VisibleBounds
children: model.children,
gaps: model.gaps,
layoutDirection: model.layoutDirection,
layoutMode: model.layoutMode,
axialAlignment: model.axialAlignment,
size: bounds.size
)
@@ -17,6 +17,7 @@ extension ContainerBlockLayout {
children: blocks.map { .init(content: $0, crossAlignment: crossAlignment) },
gaps: gaps,
layoutDirection: layoutDirection,
layoutMode: .noWrap,
axialAlignment: axialAlignment,
size: size
)