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

TextView UIViewRepresentable设置行间距时UndoManager重置问题求助

Fix Undo/Redo & Line Spacing for UIViewRepresentable TextView in SwiftUI

I've run into exactly this issue before—trying to add line spacing to a UITextView wrapped in UIViewRepresentable breaks undo/redo, and messing with the text storage while keeping the SwiftUI binding synced can cause text resets on view refreshes. Here's a solid solution that fixes both problems:

Core Approach

Instead of replacing the entire attributedText (which nukes the UndoManager), we modify the existing textStorage directly to apply line spacing. We also add guard clauses to avoid unnecessary text resets during view updates, and handle the SwiftUI binding sync correctly without breaking the undo stack.

Full Implementation

import SwiftUI
import UIKit

struct TextView: UIViewRepresentable {
    @Binding var text: String
    var lineSpacing: CGFloat = 0
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.font = UIFont.systemFont(ofSize: 16)
        
        // Apply initial line spacing
        context.coordinator.updateLineSpacing(for: textView, lineSpacing: lineSpacing)
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        // Only update text if it's actually different—prevents resets on view refreshes
        guard uiView.text != text else { return }
        
        // Temporarily disable undo registration to avoid adding programmatic text updates to the undo stack
        uiView.undoManager?.disableUndoRegistration()
        uiView.text = text
        uiView.undoManager?.enableUndoRegistration()
        
        // Refresh line spacing after updating text
        context.coordinator.updateLineSpacing(for: uiView, lineSpacing: lineSpacing)
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        var parent: TextView
        
        init(_ parent: TextView) {
            self.parent = parent
        }
        
        func textViewDidChange(_ textView: UITextView) {
            // Sync text changes back to SwiftUI binding—this doesn't break undo
            parent.text = textView.text
            
            // Ensure new text gets the correct line spacing
            updateLineSpacing(for: textView, lineSpacing: parent.lineSpacing)
        }
        
        func updateLineSpacing(for textView: UITextView, lineSpacing: CGFloat) {
            guard let textStorage = textView.textStorage else { return }
            let fullTextRange = NSRange(location: 0, length: textStorage.length)
            
            // Clear existing paragraph style to avoid conflicts
            textStorage.removeAttribute(.paragraphStyle, range: fullTextRange)
            
            // Create and apply new paragraph style with line spacing
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.lineSpacing = lineSpacing
            paragraphStyle.alignment = textView.textAlignment
            
            textStorage.addAttribute(.paragraphStyle, value: paragraphStyle, range: fullTextRange)
        }
    }
}

Key Fixes Explained

  1. Avoid Unnecessary Text Resets: The guard uiView.text != text else { return } check in updateUIView ensures we don't overwrite the text view's content during unrelated view refreshes (like switching apps or rotating the screen).
  2. Preserve Undo Stack: By modifying the existing textStorage instead of replacing attributedText, we don't trigger a reset of the text view's internal UndoManager. We also temporarily disable undo registration when updating text programmatically to avoid cluttering the undo stack.
  3. Sync Binding Without Issues: The textViewDidChange method keeps the SwiftUI binding synced to the text view's content. Since this runs after the user's text changes, it doesn't interfere with undo/redo functionality.

How to Use It

Test the solution with a simple SwiftUI view:

struct ContentView: View {
    @State private var text = "Hello, this is a test text with line spacing.\n\nMultiple lines should have the correct spacing between them. Try typing some text and using undo/redo—it should work perfectly!"
    
    var body: some View {
        NavigationStack {
            TextView(text: $text, lineSpacing: 8)
                .padding()
                .border(Color.gray)
                .navigationTitle("Line Spacing + Undo Fix")
        }
    }
}

This implementation keeps line spacing working, preserves undo/redo functionality, and prevents text resets during view updates—all the boxes checked!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 07:44:36