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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:15:50