Search code examples
swiftuikituitextviewtext-editor

Swiftui UITextView new line margin


I have a UITextView which I use as a text editor. However, I would like each new line to have a margin bottom.

Test text 1. Paragraph 1

Second paragraph. Test text 2

I have seen that it is not possible to do this from a paragraphStyle to since changing the line height

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 5

but not setting the margin for each new line.

I tried to set paragraphSpacing but the various paragraphs have the right margin at the bottom but the height of the last line of the paragraph is different from the others and shows a spaced cursor.

paragraphStyle.paragraphSpacing = 20

I tried with a regex to translate each \n to \n\n

func textViewDidChange(_ textView: UITextView) {
let newPattern = "(?<!\n)\n(?!\n)"
let regexOptions: NSRegularExpression.Options = [.anchorsMatchLines, .dotMatchesLineSeparators,]

but here too I got inaccurate results

Is it possible to set a margin bottom for each new line in a text editor?

Any ideas on how to proceed?


Solution

  • Based on this answer: https://stackoverflow.com/a/20311650/6257435 -- you can create a UITextView subclass and override caretRect(for position: UITextPosition):

    class MyTextView: UITextView {
        
        override func caretRect(for position: UITextPosition) -> CGRect {
            var superRect = super.caretRect(for: position)
            guard let font = self.font else { return superRect }
            
            // "descender" is expressed as a negative value,
            // so to add its height you must subtract its value
            superRect.size.height = font.pointSize - font.descender
            return superRect
        }
    }
    

    and here's an example - top is UITextView (yellow background) bottom is MyTextView (green background):

    class ViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let defaultTextView = UITextView()
            defaultTextView.backgroundColor = .yellow
            defaultTextView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(defaultTextView)
            
            let myTextView = MyTextView()
            myTextView.backgroundColor = .green
            myTextView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(myTextView)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                
                defaultTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                defaultTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                defaultTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                defaultTextView.heightAnchor.constraint(equalToConstant: 200.0),
                
                myTextView.topAnchor.constraint(equalTo: defaultTextView.bottomAnchor, constant: 20.0),
                myTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                myTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                myTextView.heightAnchor.constraint(equalToConstant: 200.0),
    
            ])
            
            let font = UIFont.systemFont(ofSize: 20)
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.paragraphSpacing = 32
            
            let attributes: [NSAttributedString.Key: Any] = [
                .font: font,
                .foregroundColor: UIColor.blue,
                .paragraphStyle: paragraphStyle
            ]
            
            var testString = "UITextView Paragraph 1.\nParagraph 2 will be long enough to word wrap without line feeds.\nParagraph 3"
            var attributedQuote = NSAttributedString(string: testString, attributes: attributes)
            defaultTextView.attributedText = attributedQuote
    
            testString = "MyTextView Paragraph 1.\nParagraph 2 will be long enough to word wrap without line feeds.\nParagraph 3"
            attributedQuote = NSAttributedString(string: testString, attributes: attributes)
            myTextView.attributedText = attributedQuote
    
        }
        
    }
    

    Output:

    enter image description here

    enter image description here