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

如何使Collection Cell中的DTAttributedTextContentView自适应文本尺寸?

问题:DTAttributedTextContentView在CollectionView单元格中无法自适应宽度包裹文本

我需要在CollectionView的单元格中,让DTAttributedTextContentView根据富文本内容自适应尺寸。当前设置宽度约束attributedTextContentView.widthAnchor.constraint(lessThanOrEqualToConstant: 260)时,即便富文本内容宽度小于260,该视图仍会占用全部260宽度,导致多余空白。

我最初使用UITextView,但多单元格滚动不够流畅,且需要便捷访问文本最后一行,因此希望继续使用DTAttributedTextContentView,请教如何让其框架刚好包裹文本内容。


解决方案

核心思路

利用DTLayoutManager计算富文本在最大宽度限制下的实际占用宽度,动态设置DTAttributedTextContentView的宽度约束,避免强制占用最大宽度。

修改后的完整代码

import UIKit
import DTCoreText

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        configureCollectionView()
    }

// MARK: - Collection view setup

    let collectionView: UICollectionView = {
        let layout = UICollectionViewCompositionalLayout { (section, environment) -> NSCollectionLayoutSection? in
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(10))
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitems: [item])
            let section = NSCollectionLayoutSection(group: group)
            section.interGroupSpacing = 5
            return section
        }
        layout.configuration.scrollDirection = .vertical
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)

        collectionView.register(ConversationCollectionViewCell.self, forCellWithReuseIdentifier: "ConversationCell")
        return collectionView
    }()
    
    private func configureCollectionView() {
       collectionView.dataSource = self
       collectionView.backgroundColor = .brown
       view.addSubview(collectionView)

       collectionView.translatesAutoresizingMaskIntoConstraints = false
          NSLayoutConstraint.activate([
              collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
              collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
              collectionView.topAnchor.constraint(equalTo: view.topAnchor),
              collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
       ])
    }
}

  // MARK: - Collection Data Source

 extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCell", for: indexPath) as! ConversationCollectionViewCell
        // 模拟不同长度的文本
        let testTexts = [
            "Short msg",
            "Simple message for testing purpose @",
            "A much longer text content that will wrap into multiple lines when the width reaches 260 pixels limit"
        ]
        let randomText = testTexts[indexPath.item % testTexts.count]
        let attributedString = NSAttributedString(string: randomText, attributes: [
            .font:  UIFont(name: "HelveticaNeue", size: 17) ?? UIFont.systemFont(ofSize: 17),
            .foregroundColor: UIColor.white,
            .paragraphStyle: {
                let paragraphStyle = NSMutableParagraphStyle()
                paragraphStyle.alignment = .left
                paragraphStyle.lineBreakMode = .byWordWrapping
                return paragraphStyle
            }()
        ])
        cell.setAttributedString(attributedString)
        return cell
    }
 }
  // MARK: - Collection View Custom Cell

final class ConversationCollectionViewCell: UICollectionViewCell, DTAttributedTextContentViewDelegate {

     var mainCellContainerView = UIView()
     var attributedTextContentView = DTAttributedTextContentView()
     // 保存宽度约束引用,用于动态更新
     private var widthConstraint: NSLayoutConstraint?

//MARK: - LIFECYCLE
     override init(frame: CGRect) {
        super.init(frame: frame)
    
        setupmainCellContainerView()
        setupAttributedTextContentView()
        layoutIfNeeded()
    }

   required init?(coder: NSCoder) {
       fatalError("init(coder:) has not been implemented")
   }

// MARK: - UI SETUP

   private func setupmainCellContainerView() {
       contentView.addSubview(mainCellContainerView)
       mainCellContainerView.translatesAutoresizingMaskIntoConstraints = false

       NSLayoutConstraint.activate([
           mainCellContainerView.topAnchor.constraint(equalTo: contentView.topAnchor),
           mainCellContainerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
           mainCellContainerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
           mainCellContainerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
       ])
   }
   private func setupAttributedTextContentView() {
       mainCellContainerView.addSubview(attributedTextContentView)

       attributedTextContentView.backgroundColor = .systemIndigo
       attributedTextContentView.delegate = self
       attributedTextContentView.contentMode = .redraw
       // 关闭默认自动调整,改为手动计算尺寸
       attributedTextContentView.autoresizingMask = []

       attributedTextContentView.translatesAutoresizingMaskIntoConstraints = false
       NSLayoutConstraint.activate([
           attributedTextContentView.topAnchor.constraint(equalTo: mainCellContainerView.topAnchor),
           attributedTextContentView.bottomAnchor.constraint(equalTo: mainCellContainerView.bottomAnchor),
           attributedTextContentView.leadingAnchor.constraint(equalTo: mainCellContainerView.leadingAnchor)
       ])
   }

    // 动态设置富文本并更新视图宽度
    func setAttributedString(_ string: NSAttributedString) {
        attributedTextContentView.attributedString = string
        updateTextContentViewWidth()
    }

    // 计算文本实际宽度并更新约束
    private func updateTextContentViewWidth() {
        guard let attributedString = attributedTextContentView.attributedString else { return }
        
        let maxAllowedWidth: CGFloat = 260
        // 使用DTLayoutManager计算文本在最大宽度下的实际尺寸
        let layoutManager = DTLayoutManager()
        layoutManager.attributedString = attributedString
        layoutManager.maximumWidth = maxAllowedWidth
        layoutManager.layout()
        
        // 取实际宽度和最大宽度的较小值
        let actualWidth = min(layoutManager.textBoundingRect.size.width, maxAllowedWidth)
        
        // 更新宽度约束
        widthConstraint?.isActive = false
        widthConstraint = attributedTextContentView.widthAnchor.constraint(equalToConstant: actualWidth)
        widthConstraint?.isActive = true
        
        // 触发布局刷新
        setNeedsLayout()
        layoutIfNeeded()
    }
}

关键修改点

  1. 新增宽度约束引用:保存widthConstraint,方便动态更新视图宽度
  2. 移除强制宽度约束:不再使用lessThanOrEqualToConstant,改为通过DTLayoutManager计算实际宽度后设置固定宽度
  3. 添加动态更新方法:setAttributedString(_:)用于外部传入文本时自动重新计算尺寸
  4. 使用DTLayoutManager计算尺寸:直接利用DTCoreText自带的布局管理器,精准计算富文本在指定最大宽度下的实际占用宽度

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 13:25:58