UITextView 换行未检测到

问题描述 投票:0回答:1

我有这个 Uiview 可表示的行号,它工作得足够好,但是当一行环绕行号时,不会检测到这一点。我使用了“enumerateLineFragments”,但它使用换行作为单独的行。我对 UIKit 很陌生,这是我能想到的最好的方法,因为开源库的工作效果不是特别好,而且非常复杂。预先感谢。

这是我的代码。我可以这样使用:“CodeEditor(text: $text)”

import SwiftUI

struct CodeEditor: UIViewRepresentable {
    @Binding var text: String
    
    var textView = LineNumberedTextView()
    
    func makeUIView(context: Context) -> LineNumberedTextView {
        textView.isEditable = true
        textView.delegate = context.coordinator
        textView.font = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)
        textView.backgroundColor = UIColor.white
        textView.textContainer.lineBreakMode = .byCharWrapping
        return textView
    }
    
    func updateUIView(_ uiView: LineNumberedTextView, context: Context) {
        if text != uiView.text {
            uiView.text = text
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        var parent: CodeEditor
        
        init(_ parent: CodeEditor) {
            self.parent = parent
        }
        
        func textViewDidChange(_ textView: UITextView) {
            self.parent.text = textView.text
        }
        func textViewDidChangeSelection(_ textView: UITextView) {
            self.parent.textView.currentLines = textView.text.lineNumbersForRange(textView.selectedRange) ?? []
            self.parent.textView.setNeedsLayout()
        }
    }
}

extension String {
    func lineNumbersForRange(_ range: NSRange) -> [Int]? {
        guard
            let startIndex = self.index(self.startIndex, offsetBy: range.location, limitedBy: self.endIndex),
            let endIndex = self.index(startIndex, offsetBy: range.length, limitedBy: self.endIndex)
        else {
            return nil
        }
        
        let substring = self[startIndex..<endIndex]
        var lineNumbers: [Int] = []
        
        // Find the lines within the range
        let linesInRange = substring.components(separatedBy: .newlines)
        
        // Calculate the number of newline characters before the start index
        let lineBreaksBeforeStartIndex = self[..<startIndex].components(separatedBy: .newlines).count - 1
        
        // Iterate over each line within the range
        for (index, _) in linesInRange.enumerated() {
            // Calculate the line number for each line within the range
            let lineNumber = lineBreaksBeforeStartIndex + index + 1
            lineNumbers.append(lineNumber)
        }
        
        return lineNumbers
    }
}


class LineNumberedTextView: UITextView {
    let lineNumberGutterWidth: CGFloat = 40
    let lineNumberPadding: CGFloat = 5
    
    var currentLines: [Int] = []
    
    override func draw(_ rect: CGRect) {
        
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = .right
        
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.monospacedSystemFont(ofSize: 15, weight: .ultraLight),
            .paragraphStyle: paragraphStyle,
            .foregroundColor: UIColor(white: 0.8, alpha: 1)
        ]
        let attributes_active: [NSAttributedString.Key: Any] = [
            .font: UIFont.monospacedSystemFont(ofSize: 15, weight: .ultraLight),
            .paragraphStyle: paragraphStyle,
            .foregroundColor: UIColor.black
        ]
        
        var lineNum = 1
        
        var lineRect = CGRect.zero
        
        layoutManager.enumerateLineFragments(forGlyphRange: NSRange(location: 0, length: textStorage.length)) { (rect, usedRect, textContainer, glyphRange, stop) in
            
            print("Line \(lineNum): H: \(rect.height)")
            
            lineRect = CGRect(x: 5, y: rect.origin.y + self.textContainerInset.top, width: self.lineNumberGutterWidth - self.lineNumberPadding, height: rect.height)
            let attributedString = NSAttributedString(string: "\(lineNum)", attributes: self.currentLines.contains(lineNum) ? attributes_active : attributes)
            attributedString.draw(in: lineRect)
            lineNum += 1
        }
        
        if self.textStorage.string.isEmpty {
            let attributedString = NSAttributedString(string: "\(lineNum)", attributes: self.currentLines.contains(lineNum) ? attributes_active : attributes)
            attributedString.draw(at: CGPoint(x: self.lineNumberGutterWidth - self.lineNumberPadding - 5, y: self.textContainerInset.top))
        }
        
        if self.textStorage.string.hasSuffix("\n") {
            let rect = lineRect.offsetBy(dx: 0, dy: lineRect.height)
            let attributedString = NSAttributedString(string: "\(lineNum)", attributes: self.currentLines.contains(lineNum) ? attributes_active : attributes)
            attributedString.draw(in: rect)
            print(rect)
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        textContainerInset = UIEdgeInsets(top: textContainerInset.top, left: lineNumberGutterWidth, bottom: textContainerInset.bottom, right: textContainerInset.right)
        setNeedsDisplay()
    }
    
    override var text: String! {
        didSet {
            setNeedsDisplay()
        }
    }
}

swift swiftui syntax uikit line-numbers
1个回答
0
投票

不能像Coordinator(self)那样使用self,因为这里的self是一个值而不是对象引用。

在更新中,您需要使用更改后的绑定值在使用新绑定的协调器上设置类似新闭包的内容。

make 需要初始化 UIView,所以它只会发生一次,你不应该在 SwiftUI 中将对象初始化为 struct vars,这是内存泄漏。

© www.soinside.com 2019 - 2024. All rights reserved.