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

设备旋转时UIViewRepresentable无法刷新的问题排查

解决SwiftUI中TextKit2自定义UITextView旋转后绘图不更新的问题

问题现象

将自定义UITextView封装为UIViewRepresentable并使用TextKit2绘制高亮区域时,设备旋转后绘图无法适配新布局,只有连续两次切换设备方向后才会触发正确的重绘。旋转前绘图显示正常,旋转后布局已更新但绘图区域仍停留在旧位置。

问题原因

  1. 设备旋转时,UITextView的frame更新,但TextKit2的NSTextLayoutManager未自动触发重新布局,导致获取的文本段位置还是旧的布局数据。
  2. draw(_:)方法未在布局变化后被及时调用,无法同步更新绘图内容。
  3. 原代码中绘图时的rect调整逻辑存在错误,adjustedRect被覆盖,没有应用预期的偏移和高度调整。

解决方案

1. 自定义TextView中监听布局变化并强制更新

重写layoutSubviews方法,在布局更新时让NSTextLayoutManager重新计算布局,并调用setNeedsDisplay()触发重绘。

2. 修正TextContainer配置

确保NSTextContainer的尺寸始终跟随TextView的边界,避免布局错位。

3. 修复绘图时的rect调整逻辑

保证调整后的rect被正确使用,而非被原rect覆盖。

修改后的完整代码

import SwiftUI

struct ContentView: View {
    var body: some View {
        TextViewRepresentable()
    }
}

#Preview {
    ContentView()
}

struct TextViewRepresentable: UIViewRepresentable {
    let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    func makeUIView(context: Context) -> UITextView {
        let tc = NSTextContainer(size: .zero)
        tc.heightTracksTextView = true
        tc.widthTracksTextView = true

        let lm = NSTextLayoutManager()
        lm.textContainer = tc

        let ts = NSTextContentStorage()
        ts.addTextLayoutManager(lm)

        let myTextView = MyTextView(frame: .zero, textContainer: tc)
        myTextView.font = UIFont.monospacedSystemFont(ofSize: 44, weight: .regular)
        myTextView.text = alphabet + alphabet
        
        return myTextView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        // 避免重复设置文本,仅当内容变化时更新
        guard uiView.text != alphabet + alphabet else { return }
        uiView.text = alphabet + alphabet
    }
}

class MyTextView: UITextView {
    override func layoutSubviews() {
        super.layoutSubviews()
        // 强制TextLayoutManager更新布局
        textLayoutManager?.ensureLayout(for: textContentManager?.documentRange ?? .init())
        // 触发重绘
        setNeedsDisplay()
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)

        debugPrint("draw")
        
        guard let context = UIGraphicsGetCurrentContext() else { return }

        context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)

        for rect in rects(for: NSMakeRange(3, 12)) {
            context.fill(rect)
        }
    }

    func rects(for range: NSRange) -> [CGRect] {
        var rects: [CGRect] = []

        guard let textLayoutManager = textLayoutManager, 
              let contentManager = textLayoutManager.textContentManager else {
            return []
        }
        
        guard let start = contentManager.location(contentManager.documentRange.location, offsetBy: range.location),
              let end = contentManager.location(start, offsetBy: range.length),
              let textRange = NSTextRange(location: start, end: end) else {
            return []
        }
        
        textLayoutManager.enumerateTextSegments(in: textRange, type: .standard, options: .rangeNotRequired) { _, segmentRect, _, _ in
            // 修正rect调整逻辑,避免覆盖
            var adjustedRect = segmentRect
            adjustedRect.size.height -= 4.0
            adjustedRect = adjustedRect.offsetBy(dx: 0, dy: 8.0)
            
            rects.append(adjustedRect)
            return true
        }

        return rects
    }
}

关键修改点说明

  • 在MyTextView的layoutSubviews中调用textLayoutManager?.ensureLayout(for:)强制TextKit2重新计算布局,确保获取的文本段位置是最新的。
  • 调用setNeedsDisplay()触发draw(_:)方法执行,更新绘图内容。
  • 修复了rects(for:)方法中adjustedRect被覆盖的问题,使用offsetBy替代过时的CGRectOffset。
  • 在makeUIView中直接设置初始文本,避免updateUIView重复设置导致不必要的刷新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 09:08:11