You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS AsyncDisplayKit/Texture中ASAbsoluteLayoutSpec无法拉伸内容填充宽度

问题:Texture(AsyncDisplayKit)列表单元格内容无法撑满行宽,需添加右下角三角形图标

我在iOS应用中使用AsyncDisplayKit/Texture实现列表,每行要求水平显示文本与图片,并在右下角放置绿色三角形图标。但引入ASAbsoluteLayoutSpec后,单元格内容无法撑满整个行宽,即使将其sizing设为.default也无效;移除该Spec后布局正常,但无法添加图标,尝试设置flexGrow=1和alignSelf=.stretch均无效果。

以下是原复现代码:

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的.default sizing不会自动让子元素撑满可用宽度,而是基于子元素固有尺寸计算自身大小。我们可以用ASOverlayLayoutSpec替代,将三角形图标作为叠加层放在内容布局的右下角,既保留内容的撑满布局,又能准确定位图标。

修改后的MyCellNode布局代码如下:

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")
        
        // 设置图标对齐方式为右下角
        savedIcon.style.preferredSize = CGSize(width: sizeToUse, height: sizeToUse)
        savedIcon.style.alignSelf = .end
        savedIcon.style.flexGrow = 0
    }
    
    override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
        // 内容布局:水平Stack,两端对齐撑满宽度
        let content = ASStackLayoutSpec(direction: .horizontal, spacing: sizeToUse, justifyContent: .spaceBetween, alignItems: .start, children: [title, thumbnailNode])
        // 给内容添加内边距
        let contentInset = ASInsetLayoutSpec(insets: UIEdgeInsets(top: sizeToUse, left: sizeToUse, bottom: sizeToUse, right: sizeToUse), child: content)
        
        // 给图标添加右下内边距,实现右下角定位
        let iconInset = ASInsetLayoutSpec(insets: UIEdgeInsets(top: 0, left: 0, bottom: sizeToUse, right: sizeToUse), child: savedIcon)
        
        // 用OverlaySpec将图标叠加在内容上
        let finalLayout = ASOverlayLayoutSpec(child: contentInset, overlay: iconInset)
        
        return finalLayout
    }
    
    // 移除手动修改frame的layout方法
}

关键修改说明

  • 替换ASAbsoluteLayoutSpec为ASOverlayLayoutSpec:将内容布局作为底层,图标作为叠加层,不影响内容的宽度撑满逻辑
  • 给图标添加ASInsetLayoutSpec:通过右下内边距直接定位到单元格右下角,无需手动修改frame
  • 内容布局的ASStackLayoutSpec因justifyContent: .spaceBetween会自动撑满可用宽度,无需额外设置flexGrow

内容的提问来源于stack exchange,提问作者sudoExclamationExclamation

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 12:47:08