Mac端iPad适配App中UICollectionViewCell内嵌UITextView致CPU飙升
问题描述
我开发了一个简单的UIKit应用,在UICollectionViewCell中内嵌了UITextView,该应用在iOS/iPadOS平台运行正常,但在Mac(iPad适配模式)下,滚动UICollectionView时CPU占用率会飙升至约85%并持续居高不下,仅当点击应用窗口外部切换到后台时CPU占用才会下降,切回前台后又立刻回升。我也尝试了Catalyst模式,同样存在该问题,只是CPU占用率略低(约45%)。
此外,滚动时调试器持续输出日志:[API] cannot add handler to 3 from 3 - dropping
请问有人能解释该问题或提供解决方案吗?
环境信息
- Xcode Version 14.1 (14B47b)
- macOS Ventura 13.0 (22A380)
代码示例
ViewController代码
class ViewController: UIViewController { var dataSource: UICollectionViewDiffableDataSource<Section, String>! = nil var collectionView: UICollectionView! = nil var items = Array(0...100).map{"Item \($0)"} enum Section: String { case main } override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "List" configureCollectionView() configureDataSource() applyInitialSnapshot() } private func createLayout() -> UICollectionViewLayout { return UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100)) let item = NSCollectionLayoutItem(layoutSize: size) let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item]) return NSCollectionLayoutSection(group: group) } } private func configureCollectionView() { collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.backgroundColor = .systemBackground view.addSubview(collectionView) } private func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<TestCell, String> { (cell, indexPath, item) in cell.configure(title: item, row: indexPath.item) } dataSource = UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) { (collectionView, indexPath, identifier) -> UICollectionViewCell? in return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier) } } private func applyInitialSnapshot() { var snapshot = NSDiffableDataSourceSnapshot<Section, String>() snapshot.appendSections([.main]) snapshot.appendItems(items) dataSource.apply(snapshot, animatingDifferences: false) } }
TestCell代码
class TestCell: UICollectionViewCell { private let annotationsTextView = UITextView() override init(frame: CGRect) { super.init(frame: frame) addViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configure(title: String, row: Int) { annotationsTextView.attributedText = .init(string: "Row: \(row) Item: \(title)", attributes: [.font: UIFont.preferredFont(forTextStyle: .title1)]) } private func addViews() { annotationsTextView.isScrollEnabled = false annotationsTextView.isEditable = false annotationsTextView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(annotationsTextView) NSLayoutConstraint.activate([ annotationsTextView.topAnchor.constraint(equalTo: contentView.topAnchor), annotationsTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), annotationsTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), annotationsTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), ]) } }
问题原因与解决方案
问题原因
这个问题源于UITextView在Mac的iPad适配/Catalyst模式下的底层交互逻辑冗余:即使禁用了滚动和编辑功能,UITextView仍会保留文本选择、富文本编辑等相关的后台处理逻辑,当CollectionView滚动复用单元格时,这些逻辑会被频繁触发,导致大量不必要的计算,进而引发CPU占用飙升。调试日志[API] cannot add handler to 3 from 3 - dropping也反映了UITextView在Mac平台上的交互处理存在内部冲突。
解决方案
方案1:替换UITextView为UILabel(推荐)
由于你的场景仅需展示文本,无需编辑或滚动功能,UILabel是更轻量、性能更稳定的替代方案,完全支持富文本展示。修改后的TestCell代码如下:
class TestCell: UICollectionViewCell { private let annotationsLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) addViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configure(title: String, row: Int) { annotationsLabel.attributedText = .init(string: "Row: \(row) Item: \(title)", attributes: [.font: UIFont.preferredFont(forTextStyle: .title1)]) } private func addViews() { annotationsLabel.numberOfLines = 0 // 支持多行文本换行 annotationsLabel.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(annotationsLabel) NSLayoutConstraint.activate([ annotationsLabel.topAnchor.constraint(equalTo: contentView.topAnchor), annotationsLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), annotationsLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), annotationsLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), ]) } }
方案2:优化UITextView配置(需保留UITextView时使用)
若因业务需求必须使用UITextView,可通过禁用更多不必要的交互功能来降低CPU占用:
private func addViews() { annotationsTextView.isScrollEnabled = false annotationsTextView.isEditable = false annotationsTextView.isSelectable = false // 禁用文本选择 annotationsTextView.allowsEditingTextAttributes = false // 禁用富文本编辑 annotationsTextView.textContainerInset = .zero // 重置内边距,减少布局计算 annotationsTextView.textContainer.lineFragmentPadding = 0 // 重置行内边距 annotationsTextView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(annotationsTextView) NSLayoutConstraint.activate([ annotationsTextView.topAnchor.constraint(equalTo: contentView.topAnchor), annotationsTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), annotationsTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), annotationsTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), ]) }
这些设置能减少UITextView的后台计算量,一定程度缓解CPU占用问题,但效果不如替换为UILabel显著。
内容的提问来源于stack exchange,提问作者Richard Witherspoon

