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

UITextView在段落中途修改字号时出现额外空白,如何实现平滑切换?

UITextView字体平滑切换解决方案

通常修改UITextView字体的方式是直接赋值:

textView.font = .systemFont(ofSize: 20)

但如果在长文本的中间或末尾修改字号,滚动到顶部时,UITextView会出现大片空白并产生生硬的重排。要实现像苹果Books应用那样的平滑字体切换,可以参考以下几种方案:

方案1:基于滚动比例保持相对位置

修改字体前记录当前滚动位置相对于总内容高度的比例,待字体更新、布局重新计算后,再将滚动位置设置为对应比例的新偏移量,确保阅读位置的相对一致性:

@objc func handleChange() {
    // 计算当前滚动位置的比例
    let contentHeight = textView.contentSize.height
    let visibleHeight = textView.bounds.height
    let scrollOffsetY = textView.contentOffset.y
    let scrollRatio = scrollOffsetY / max(contentHeight - visibleHeight, 1)
    
    // 修改字体
    textView.font = .systemFont(ofSize: 20)
    
    // 等待布局更新完成后调整滚动位置
    DispatchQueue.main.async {
        let newContentHeight = self.textView.contentSize.height
        let newOffsetY = scrollRatio * max(newContentHeight - visibleHeight, 0)
        self.textView.setContentOffset(CGPoint(x: 0, y: newOffsetY), animated: true)
    }
}

方案2:用CATransaction实现平滑过渡动画

将字体修改与滚动位置调整放入同一个动画事务中,让布局变化和滚动调整同步进行,视觉上更连贯:

@objc func handleChange() {
    let contentHeight = textView.contentSize.height
    let visibleHeight = textView.bounds.height
    let scrollOffsetY = textView.contentOffset.y
    let scrollRatio = scrollOffsetY / max(contentHeight - visibleHeight, 1)
    
    CATransaction.begin()
    CATransaction.setAnimationDuration(0.3) // 匹配系统默认动画时长
    
    // 修改字体触发布局更新
    textView.font = .systemFont(ofSize: 20)
    
    // 布局完成后调整滚动位置
    CATransaction.setCompletionBlock {
        let newContentHeight = self.textView.contentSize.height
        let newOffsetY = scrollRatio * max(newContentHeight - visibleHeight, 0)
        self.textView.setContentOffset(CGPoint(x: 0, y: newOffsetY), animated: false)
    }
    
    CATransaction.commit()
}

方案3:基于文本位置保持阅读上下文(模拟苹果Books逻辑)

苹果Books应该是通过记录当前阅读的文本位置而非滚动偏移量来实现平滑切换。我们可以获取当前可见区域的起始文本位置,修改字体后再滚动到该位置,确保用户阅读的上下文不变:

@objc func handleChange() {
    // 获取当前可见区域的起始文本位置
    guard let startPosition = textView.visibleTextRange?.start else { return }
    
    // 修改字体
    textView.font = .systemFont(ofSize: 20)
    
    // 等待布局更新后,滚动到之前记录的文本位置
    DispatchQueue.main.async {
        let targetRange = self.textView.textRange(from: startPosition, to: startPosition)
        self.textView.scrollRangeToVisible(targetRange!)
    }
}

注意事项

  • 必须等待主线程布局周期完成后再调整滚动位置,因为字体修改后contentSize不会立即更新,异步处理(DispatchQueue.main.async)可以确保获取到最新的布局参数。
  • 如果文本内容是动态加载的,需要确保内容加载完成后再执行字体切换逻辑。

内容的提问来源于stack exchange,提问作者yaozhang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 08:35:35