UITextView滚动指示器顶部截断问题及官方实现方案咨询
UICollectionViewCell中UITextView滚动指示器截断问题与官方实现参考
示例应用
采用列表布局的UICollectionViewController,包含1个分区和1行。单元格的contentView内包含一个UITextView。
class ViewController: UICollectionViewController { var snapshot: NSDiffableDataSourceSnapshot<Section, String> { var snapshot = NSDiffableDataSourceSnapshot<Section, String>() snapshot.appendSections([.main]) snapshot.appendItems(["one", "two"], toSection: .main) return snapshot } var dataSource: UICollectionViewDiffableDataSource<Section, String>? enum Section { case main } init() { super.init(collectionViewLayout: .init()) collectionView.collectionViewLayout = createLayout() configureDataSource() // 更推荐在init中配置数据源,可自动避免iOS 15上的不愉快动画,而非在viewDidLoad中配置 } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in let textView = UITextView() textView.font = .systemFont(ofSize: UIFont.labelFontSize) cell.contentView.addSubview(textView) textView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ textView.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 8), textView.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -8), textView.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: cell.directionalLayoutMargins.leading), textView.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -cell.directionalLayoutMargins.trailing) ]) } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } dataSource?.apply(self.snapshot, animatingDifferences: false) } func createLayout() -> UICollectionViewLayout { return UICollectionViewCompositionalLayout { section, layoutEnvironment in let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) } } }
问题1:修复UITextView垂直滚动指示器顶部截断问题
修改后的核心代码
func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in // 复用UITextView,避免重复创建实例 let textViewTag = 1001 let textView: UITextView if let existingTextView = cell.contentView.viewWithTag(textViewTag) as? UITextView { textView = existingTextView } else { textView = UITextView() textView.tag = textViewTag textView.font = .systemFont(ofSize: UIFont.labelFontSize) // 允许滚动指示器超出UITextView边界显示 textView.clipsToBounds = false // 设置滚动指示器内边距,匹配单元格上下内边距 textView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) cell.contentView.addSubview(textView) textView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ textView.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 8), textView.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -8), textView.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: cell.directionalLayoutMargins.leading), textView.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -cell.directionalLayoutMargins.trailing) ]) // 禁止contentView裁剪子视图,确保滚动指示器完整显示 cell.contentView.clipsToBounds = false } textView.text = itemIdentifier } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } dataSource?.apply(self.snapshot, animatingDifferences: false) }
核心修复逻辑
- 关闭
UITextView.clipsToBounds:默认值为true,会裁剪超出文本视图边界的滚动指示器,关闭后允许指示器部分区域在文本视图外显示 - 关闭
cell.contentView.clipsToBounds:避免contentView裁剪掉文本视图外的指示器部分 - 匹配内边距设置
scrollIndicatorInsets:让滚动指示器的位置与文本视图的内边距对齐,保证视觉一致性 - 复用UITextView:优化单元格复用性能,避免每次创建新实例
问题2:Table View Cell中UITextView的官方实现参考
苹果官方在UITableViewCell中集成UITextView的标准实现逻辑如下:
- 自定义单元格布局:推荐创建UITableViewCell子类,将UITextView添加到cell的contentView中,通过Auto Layout约束控制文本视图与cell内边距的间距
- 自适应高度配置:配合UITableView的
estimatedRowHeight和rowHeight = UITableView.automaticDimension,实现根据文本内容自动调整单元格高度;若不需要文本滚动,需设置textView.isScrollEnabled = false - 官方文档参考:
- 《UIKit User Interface Catalog》中UITableViewCell章节明确了可嵌入滚动文本视图的场景
- 《Auto Layout Guide》详细介绍了动态内容单元格的约束设置方法,确保文本视图与单元格布局协同工作
已尝试但无效的方案
textView.verticalScrollIndicatorInsets.top = 30(无效果)- 将UITextView添加到自定义视图后再加入单元格contentView
- 设置以下属性:
textView.contentInset = .zero textView.scrollIndicatorInsets = .zero textView.textContainerInset = .zero textView.textContainer.lineFragmentPadding = 0 - 将UITextView垂直居中,并约束其高度与contentView高度相差8pt以保留内边距
- 将UITextView的上下锚点约束到单元格的layout margins guide的上下锚点
约束条件
出于美观需求,UITextView与单元格顶部和底部需保留一定内边距。
内容的提问来源于stack exchange,提问作者Filippo
相关产品推荐
相关产品推荐

