设备旋转时UIViewRepresentable无法刷新的问题排查
解决SwiftUI中TextKit2自定义UITextView旋转后绘图不更新的问题
问题现象
将自定义UITextView封装为UIViewRepresentable并使用TextKit2绘制高亮区域时,设备旋转后绘图无法适配新布局,只有连续两次切换设备方向后才会触发正确的重绘。旋转前绘图显示正常,旋转后布局已更新但绘图区域仍停留在旧位置。
问题原因
- 设备旋转时,UITextView的frame更新,但TextKit2的
NSTextLayoutManager未自动触发重新布局,导致获取的文本段位置还是旧的布局数据。 draw(_:)方法未在布局变化后被及时调用,无法同步更新绘图内容。- 原代码中绘图时的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
相关产品推荐
相关产品推荐

