我在 iOS 应用程序中使用
AsyncDisplayKit/Texture
来显示一个简单的列表。在每一行上,我想水平显示一些文本和图像,并在该行的右下角显示一个三角形。我有以下简单的代码可以演示整个问题:
import UIKit
import SnapKit
import AsyncDisplayKit
class ViewController: UIViewController, ASTableDataSource {
let tableNode = ASTableNode()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubnode(tableNode)
tableNode.view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
tableNode.dataSource = self
}
func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
let row = indexPath.row
return {
return MyCellNode(str: "Row \(row)")
}
}
}
let sizeToUse = 15.0
class MyCellNode: ASCellNode {
private var title = ASTextNode()
private let savedIcon = SaveIconNode()
private var thumbnailNode = ASNetworkImageNode()
init(str : String) {
super.init()
automaticallyManagesSubnodes = true
automaticallyRelayoutOnSafeAreaChanges = true
automaticallyRelayoutOnLayoutMarginsChanges = true
title.attributedText = NSAttributedString(string: str, attributes: [.foregroundColor:UIColor.white,.font:UIFont.systemFont(ofSize: 30, weight: .medium)])
title.backgroundColor = .darkGray
thumbnailNode = ASNetworkImageNode()
thumbnailNode.isLayerBacked = true
thumbnailNode.cornerRoundingType = .precomposited
thumbnailNode.cornerRadius = sizeToUse
thumbnailNode.style.preferredSize = CGSize(width: 60, height: 60)
thumbnailNode.url = URL(string: "https://favicon.mars51.com/instagram.com")
}
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let content = ASStackLayoutSpec(direction: .horizontal, spacing: sizeToUse, justifyContent: .spaceBetween, alignItems: .start, children: [title,thumbnailNode])
let inseted = ASInsetLayoutSpec(insets: UIEdgeInsets(top: sizeToUse, left: sizeToUse, bottom: sizeToUse, right: sizeToUse), child: content)
savedIcon.style.preferredSize = CGSize(width: sizeToUse, height: sizeToUse)
savedIcon.style.layoutPosition = CGPoint(x: constrainedSize.max.width - sizeToUse, y: 0)
let finalLayout = ASAbsoluteLayoutSpec(sizing: .default, children: [inseted,savedIcon])
return finalLayout
}
override func layout() {
super.layout()
var f = savedIcon.frame
f.origin.y = calculatedSize.height - f.height
savedIcon.frame = f
}
}
class SaveIconNode: ASDisplayNode {
override init() {
super.init()
isLayerBacked = true
backgroundColor = .clear
isOpaque = false
}
override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled isCancelledBlock: () -> Bool, isRasterizing: Bool) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.beginPath()
context.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
context.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
context.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
context.closePath()
UIColor.green.setFill()
context.fillPath()
}
}
渲染如下:
如您所见,行中的内容并未拉伸行的整个宽度。这似乎是由
ASAbsoluteLayoutSpec
引起的。我不确定为什么会发生以及如何解决它。请注意,我已在 sizing: .default
上指定了 ASAbsoluteLayoutSpec
,并且文档指出:
https://texturegroup.org/docs/layout2-layoutspec-types.html
sizing:确定绝对规格将占用多少空间。选项包括:默认和适合的大小。
在源代码中,它说
default
将 The spec will take up the maximum size possible.
:
/** How much space the spec will take up. */
typedef NS_ENUM(NSInteger, ASAbsoluteLayoutSpecSizing) {
/** The spec will take up the maximum size possible. */
ASAbsoluteLayoutSpecSizingDefault,
/** Computes a size for the spec that is the union of all children's frames. */
ASAbsoluteLayoutSpecSizingSizeToFit,
};
所以我认为我的代码是正确的。
如果我不包含
ASAbsoluteLayoutSpec
而只是包含 return inseted
,那么看起来还不错:
但是我无法包含我的
savedIcon
三角形。
我尝试在所有节点上设置
.style.flexGrow = 1
和 .style.alignSelf = .stretch
,但这没有任何区别。
我以不同的方式找到了解决方案。
我去掉了
func layout()
功能。
我将
savedIcon
放入 ASInsetLayoutSpec
中,其 top
和 left
设置为 CGFloat.infinity
。
我返回了一个
ASOverlayLayoutSpec
,其子级是 inseted
内容,覆盖层是上面创建的 savedIconSpec
。
我从下面的
Photo with Inset Text Overlay
示例中了解到了这个技巧:
https://texturegroup.org/docs/automatic-layout-examples-2.html
现在我的
layoutSpecThatFits
看起来像这样:
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let content = ASStackLayoutSpec(direction: .horizontal, spacing: sizeToUse, justifyContent: .spaceBetween, alignItems: .start, children: [title,thumbnailNode])
let inseted = ASInsetLayoutSpec(insets: UIEdgeInsets(top: sizeToUse, left: sizeToUse, bottom: sizeToUse, right: sizeToUse), child: content)
savedIcon.style.preferredSize = CGSize(width: sizeToUse, height: sizeToUse)
savedIcon.style.layoutPosition = CGPoint(x: constrainedSize.max.width - sizeToUse, y: 0)
let savedIconSpec = ASInsetLayoutSpec(insets: UIEdgeInsets(top: CGFloat.infinity, left: CGFloat.infinity, bottom: 0, right: 0), child: savedIcon)
return ASOverlayLayoutSpec(child: inseted, overlay: savedIconSpec)
}