I am using AsyncDisplayKit/Texture
in my iOS app to display a simple list. On each row, I want to display some text and an image horizontally and at the very bottom right corner of the row, I want to display a triangle. I have the following simple code which can demonstrate the entire issue:
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()
}
}
This renders as such:
As you can see, the content in the rows aren't stretching the entire width of the row. This seems to be caused by ASAbsoluteLayoutSpec
. I am not sure why it's happening and how to fix it. Note that I have specified sizing: .default
on the ASAbsoluteLayoutSpec
and documents state that:
https://texturegroup.org/docs/layout2-layoutspec-types.html
sizing: Determines how much space the absolute spec will take up. Options include: Default, and Size to Fit.
And in the source code, it says default
will 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,
};
So I think my code is correct.
If I don't include the ASAbsoluteLayoutSpec
and simply return inseted
, then it looks okay:
But then I am not able to include my savedIcon
triangle.
I have tried setting .style.flexGrow = 1
and .style.alignSelf = .stretch
on all the nodes but that didn't make any difference whatsoever.
I figured out the solution in a different way.
I got rid of the func layout()
function.
I put the savedIcon
inside an ASInsetLayoutSpec
whose top
and left
are set to CGFloat.infinity
.
I returned a ASOverlayLayoutSpec
whose child is the inseted
content and overlay is the above created savedIconSpec
.
I learnt about this trick from the Photo with Inset Text Overlay
example below:
https://texturegroup.org/docs/automatic-layout-examples-2.html
Now my layoutSpecThatFits
looks like this:
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)
}
It looks like this now: