如何使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() } }
关键修改点
- 新增宽度约束引用:保存
widthConstraint,方便动态更新视图宽度 - 移除强制宽度约束:不再使用
lessThanOrEqualToConstant,改为通过DTLayoutManager计算实际宽度后设置固定宽度 - 添加动态更新方法:
setAttributedString(_:)用于外部传入文本时自动重新计算尺寸 - 使用DTLayoutManager计算尺寸:直接利用DTCoreText自带的布局管理器,精准计算富文本在指定最大宽度下的实际占用宽度
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

