在ScrollView中用UILabel显示整章书籍内容报错,求CATiledLayer实现方法
嘿,这个问题我之前也碰到过!你看到的这个错误是因为UILabel的内容高度实在太夸张了——88万多像素的高度,对应的 backing store 直接超过了Core Animation图层的尺寸上限,系统自然就直接忽略渲染了。CATiledLayer确实是解决这类超大文本渲染的绝佳方案,它会把内容分成一小块一小块的瓦片来按需绘制,不会一次性加载整个超大图层,下面给你一步步的实现思路和代码示例:
实现步骤
1. 创建自定义TiledTextView,指定CATiledLayer作为图层
首先我们需要一个自定义UIView,让它的底层图层使用CATiledLayer,这样才能实现分块渲染:
import UIKit class TiledTextView: UIView { // 指定图层类型为CATiledLayer override class var layerClass: AnyClass { return CATiledLayer.self } // 要显示的完整文本 var fullText: String = "" // 文本的属性(字体、行间距等) var textAttributes: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 16), .foregroundColor: UIColor.black ] // 文本的总尺寸,用于设置ScrollView的contentSize var textTotalSize: CGSize = .zero override init(frame: CGRect) { super.init(frame: frame) setupTiledLayer() } required init?(coder: NSCoder) { super.init(coder: coder) setupTiledLayer() } private func setupTiledLayer() { guard let tiledLayer = layer as? CATiledLayer else { return } // 设置瓦片大小,一般设为屏幕尺寸或者稍大一点,适配不同设备 tiledLayer.tileSize = CGSize(width: UIScreen.main.bounds.width, height: 480) // 设置contentsScale适配Retina屏幕,避免绘制模糊 tiledLayer.contentsScale = UIScreen.main.scale } }
2. 计算文本总尺寸,配置ScrollView
在使用自定义视图前,我们需要先算出完整文本的总高度,这样才能设置ScrollView的contentSize,让用户可以滚动查看全部内容:
// 假设你已经有了ScrollView实例scrollView,以及要显示的章节内容bookChapterText let tiledTextView = TiledTextView() tiledTextView.fullText = bookChapterText // 计算文本总尺寸 let maxWidth = scrollView.bounds.width let textRect = tiledTextView.fullText.boundingRect( with: CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: tiledTextView.textAttributes, context: nil ) tiledTextView.textTotalSize = textRect.size // 配置自定义视图和ScrollView tiledTextView.frame = CGRect(x: 0, y: 0, width: maxWidth, height: tiledTextView.textTotalSize.height) scrollView.addSubview(tiledTextView) scrollView.contentSize = tiledTextView.textTotalSize
3. 实现分块绘制文本的逻辑
接下来在自定义TiledTextView的draw(_ rect: CGRect)方法里,只绘制当前rect范围内的文本,这样CATiledLayer就会按需加载可见区域的瓦片:
extension TiledTextView { override func draw(_ rect: CGRect) { super.draw(rect) // 创建带属性的文本 let attributedText = NSAttributedString(string: fullText, attributes: textAttributes) // 配置文本容器、布局管理器和文本存储 let textContainer = NSTextContainer(size: CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)) textContainer.lineFragmentPadding = 0 let layoutManager = NSLayoutManager() let textStorage = NSTextStorage(attributedString: attributedText) textStorage.addLayoutManager(layoutManager) layoutManager.addTextContainer(textContainer) // 计算当前rect对应的文本范围 let glyphRange = layoutManager.glyphRange(forBoundingRect: rect, in: textContainer) // 绘制指定范围的文本 layoutManager.drawGlyphs(forGlyphRange: glyphRange, at: rect.origin) } }
一些注意事项
- 瓦片大小调整:tileSize可以根据需求调整,太小会增加绘制次数,太大可能还是会碰到尺寸限制,一般设置为屏幕高度的1-2倍比较合适。
- 文本属性统一:确保计算总尺寸和绘制时使用的textAttributes完全一致,不然会出现布局错位。
- 性能优化:如果文本里有复杂的属性(比如不同字体、颜色),可以提前创建好NSAttributedString存在自定义视图里,避免每次绘制都重复创建。
这样实现之后,你的超大章节内容就可以在ScrollView里正常滚动显示,不会再出现那个图层尺寸错误了。
内容的提问来源于stack exchange,提问作者armnotstrong
相关产品推荐
相关产品推荐

