首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >将UITextView属性化字符串保存到SwiftUI中

将UITextView属性化字符串保存到SwiftUI中
EN

Stack Overflow用户
提问于 2021-01-22 03:46:39
回答 2查看 1.1K关注 0票数 2

我需要使用SwiftUI中的属性字符串(SwiftUI)来制作一个简单的富文本编辑器,正如您可能已经知道的那样,属性化字符串在SwiftUI中并不是本机支持的。因此,我不得不使用UITextView包装器来处理旧的UIViewRepresentable

现在,我的应用程序是一个基于文档的应用程序,每当我试图保存文件时,都会出现一些奇怪的问题:

第一个问题:当我运行应用程序并打开一个文件并开始键入时,将删除该文件的初始内容。

第二个问题:每当我编写文本并按后箭头保存文件时,它都不会更新。所有文档的初始内容仍然相同。

文档处理代码是在创建新的基于SwiftUI文档的应用程序时出现的默认代码,但是,我将编码从纯文本更改为NSMutableAttributedString。(我还创建了一个名为.mxt而不是.txt的文档扩展)

MyextDocument.swift__ 文档处理文件

代码语言:javascript
运行
复制
import SwiftUI
import UniformTypeIdentifiers

extension UTType {
    static var MyextDocument = UTType(exportedAs: "com.example.Myext.mxt")
}

struct MyextDocument: FileDocument {
    var text: NSMutableAttributedString

    init(text: NSMutableAttributedString = NSMutableAttributedString()) {
        self.text = text
    }

    static var readableContentTypes: [UTType] { [.MyextDocument] }
    
    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = try? NSMutableAttributedString(data: data, options: [NSMutableAttributedString.DocumentReadingOptionKey.documentType : NSMutableAttributedString.DocumentType.rtf], documentAttributes: nil)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        text = string
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = (try? text.data(from: NSMakeRange(0, text.length), documentAttributes: [.documentType: NSMutableAttributedString.DocumentType.rtf]))!
        return .init(regularFileWithContents: data)
    }
}

iOSEditorTextView.swift__ UIViewRepresentable包装文件

代码语言:javascript
运行
复制
import Combine
import SwiftUI
import UIKit

struct iOSEditorTextView: UIViewRepresentable {
    //@Binding var text: String
    @Binding var document: NSMutableAttributedString
    var isEditable: Bool = true
    var font: UIFont?    = .systemFont(ofSize: 14, weight: .regular)
    
    var onEditingChanged: () -> Void       = {}
    var onCommit        : () -> Void       = {}
    var onTextChange    : (String) -> Void = { _ in }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> CustomTextView {
        let textView = CustomTextView(
            text: document,
            isEditable: isEditable,
            font: font
        )
        textView.delegate = context.coordinator
        
        return textView
    }
    
    func updateUIView(_ uiView: CustomTextView, context: Context) {
        uiView.text = document
        uiView.selectedRanges = context.coordinator.selectedRanges
    }
}

// MARK: - Preview

#if DEBUG

struct iOSEditorTextView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            iOSEditorTextView(
                document: .constant(NSMutableAttributedString()),
                isEditable: true,
                font: .systemFont(ofSize: 14, weight: .regular)
            )
            .environment(\.colorScheme, .dark)
            .previewDisplayName("Dark Mode")
            
            iOSEditorTextView(
                document: .constant(NSMutableAttributedString()),
                isEditable: false
            )
            .environment(\.colorScheme, .light)
            .previewDisplayName("Light Mode")
        }
    }
}

#endif

// MARK: - Coordinator

extension iOSEditorTextView {
    
    class Coordinator: NSObject, UITextViewDelegate {
        var parent: iOSEditorTextView
        var selectedRanges: [NSValue] = []
        
        init(_ parent: iOSEditorTextView) {
            self.parent = parent
        }
        
        func textViewDidBeginEditing(_ textView: UITextView) {
            self.parent.document = textView.attributedText as! NSMutableAttributedString
            self.parent.onEditingChanged()
        }
        
        func textViewDidChange(_ textView: UITextView) {
            self.parent.document = textView.attributedText as! NSMutableAttributedString
            //self.selectedRanges = textView.selectedRange
        }

        func textViewDidEndEditing(_ textView: UITextView) {
            self.parent.document = textView.attributedText as! NSMutableAttributedString
            self.parent.onCommit()
        }
    }
}

// MARK: - CustomTextView

final class CustomTextView: UIView, UIGestureRecognizerDelegate, UITextViewDelegate {
    private var isEditable: Bool
    private var font: UIFont?
    
    weak var delegate: UITextViewDelegate?
    
    var text: NSMutableAttributedString {
        didSet {
            textView.attributedText = text
        }
    }
    
    var selectedRanges: [NSValue] = [] {
        didSet {
            guard selectedRanges.count > 0 else {
                return
            }
            
            //textView.selectedRanges = selectedRanges
        }
    }
        
    private lazy var textView: UITextView = {
        let textView                     = UITextView(frame: .zero)
        textView.delegate                = self.delegate
        textView.font                    = self.font
        textView.isEditable              = self.isEditable
        textView.textColor               = UIColor.label
        textView.textContainerInset      = UIEdgeInsets(top: 40, left: 0, bottom: 0, right: 0)
        textView.translatesAutoresizingMaskIntoConstraints = false
        
        return textView
    }()

    // Create paragraph styles
    let paragraphStyle = NSMutableParagraphStyle() // create paragraph style

    var attributes: [NSMutableAttributedString.Key: Any] = [
        .foregroundColor: UIColor.red,
        .font: UIFont(name: "Courier", size: 12)!
    ]
    
    // MARK: - Init
    init(text: NSMutableAttributedString, isEditable: Bool, font: UIFont?) {
        self.font       = font
        self.isEditable = isEditable
        self.text       = text
        
        super.init(frame: .zero)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Life cycle
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        setupTextView()
        
        // Set tap gesture
        let tap = UITapGestureRecognizer(target: self, action: #selector(didTapTextView(_:)))
        tap.delegate = self
        textView.addGestureRecognizer(tap)

        // create paragraph style
        self.paragraphStyle.headIndent = 108
        
        // create attributed string
        let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
        
        // create attributes
        self.attributes = [
            .foregroundColor: UIColor.red,
            .font: UIFont(name: "Courier", size: 12)!,
            .paragraphStyle: paragraphStyle,
        ]

        // Create the Attributed String
        let myAttrString = NSMutableAttributedString(string: string, attributes: attributes)
        
        // Write it to the Text View
        textView.attributedText = myAttrString
    }
    
    // Show cursor and set it to position on tapping + Detect line
    @objc func didTapTextView(_ recognizer: UITapGestureRecognizer) {
        // Show cursor and set it to position on tapping
        if recognizer.state == .ended {
            textView.isEditable = true
            textView.becomeFirstResponder()
                        
            let location = recognizer.location(in: textView)
            if let position = textView.closestPosition(to: location) {
                let uiTextRange = textView.textRange(from: position, to: position)
                
                if let start = uiTextRange?.start, let end = uiTextRange?.end {
                    let loc = textView.offset(from: textView.beginningOfDocument, to: position)
                    let length = textView.offset(from: start, to: end)
                    
                    textView.selectedRange = NSMakeRange(loc, length)
                }
            }
            
        }
        
    }
        
    func setupTextView() {
        // Setup Text View delegate
        textView.delegate = self
        
        // Place the Text View on the view
        addSubview(textView)
        
        NSLayoutConstraint.activate([
            textView.topAnchor.constraint(equalTo: topAnchor),
            textView.trailingAnchor.constraint(equalTo: trailingAnchor),
            textView.leadingAnchor.constraint(equalTo: leadingAnchor),
            textView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])

    }
}

为了调用ContentView__包装器,我在中编写了以下代码

代码语言:javascript
运行
复制
iOSEditorTextView(
    document: $document.text,
    isEditable: true,
    font: .systemFont(ofSize: 14, weight: .regular)
)

任何帮助都将不胜感激。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-01-22 05:46:43

代码中有几个错误。

  1. 您设置了错误的委托。因此,您的委托方法无法工作。给予自我授权,而不是自我。就像

代码语言:javascript
运行
复制
func setupTextView() {
    // Setup Text View delegate
    textView.delegate = delegate

  1. 在委托中使用NSMutableAttributedString .

代码语言:javascript
运行
复制
func textViewDidBeginEditing(_ textView: UITextView) {
    self.parent.document = NSMutableAttributedString(attributedString: textView.attributedText)
    self.parent.onEditingChanged()
}

func textViewDidChange(_ textView: UITextView) {
    self.parent.document = NSMutableAttributedString(attributedString: textView.attributedText)
}

func textViewDidEndEditing(_ textView: UITextView) {
    self.parent.document = NSMutableAttributedString(attributedString: textView.attributedText)
    self.parent.onCommit()
}

  1. override func draw(_ rect: CGRect)中删除静态文本--这一行重写现有文本.

代码语言:javascript
运行
复制
override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        setupTextView()
        
        // Set tap gesture
        let tap = UITapGestureRecognizer(target: self, action: #selector(didTapTextView(_:)))
        tap.delegate = self
        textView.addGestureRecognizer(tap)

        // create paragraph style
        self.paragraphStyle.headIndent = 108
        
        // create attributes
        self.attributes = [
            .foregroundColor: UIColor.red,
            .font: UIFont(name: "Courier", size: 12)!,
            .paragraphStyle: paragraphStyle,
        ]
    }

注意:删除其他代码,并使用init或func awakeFromNib()

票数 1
EN

Stack Overflow用户

发布于 2021-01-22 05:54:53

拉贾的所有问题都是正确的。我要做的另一件事是,我不会将struct值作为委托传递给您的Coordinator,因为您不能保证它的同一个实例在后面是可用的。将绑定传递给可变字符串要好得多。所以:

代码语言:javascript
运行
复制
extension iOSEditorTextView {
    
    class Coordinator: NSObject, UITextViewDelegate {
        var documentBinding : Binding<NSMutableAttributedString>
        var selectedRanges: [NSValue] = []
        
        init(_ documentBinding: Binding<NSMutableAttributedString>) {
            self.documentBinding = documentBinding
        }
        
        func textViewDidBeginEditing(_ textView: UITextView) {
            documentBinding.wrappedValue = NSMutableAttributedString(attributedString: textView.attributedText)
        }
        
        func textViewDidChange(_ textView: UITextView) {
            documentBinding.wrappedValue = NSMutableAttributedString(attributedString: textView.attributedText)
            //self.selectedRanges = textView.selectedRange
        }

        func textViewDidEndEditing(_ textView: UITextView) {
            documentBinding.wrappedValue = NSMutableAttributedString(attributedString: textView.attributedText)
        }
    }
}

然后:

代码语言:javascript
运行
复制
func makeCoordinator() -> Coordinator {
        Coordinator($document)
    }
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65839021

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档