user46938
user46938

Reputation: 59

NSLayoutManager for Line Numbering in UITextVIew

I am attempting to create an iOS LineNumberLayoutManger in Swift to be used in a TextKit 2 UITextView. My problem is the line number variable is being overwritten when I enter a new line with the return key. The revised code sample below can be copied and pasted into an Xcode project in a ViewController to run. Any suggestions very much appreciated.

    //  ViewController.swift
    //  tester11
    //
    //  Created by ianshortreed on 2022/07/15.
    //
    
    import UIKit
    
    var currentRect:CGRect!
    var cgPoint:CGPoint!
    
    extension String {
        
        func substring(with range: NSRange) -> String {
            let startIndex = index(self.startIndex, offsetBy: range.location)
            let endIndex = index(startIndex, offsetBy: range.length)
            
            //return substring(with: startIndex ..< endIndex)
            return String(self[startIndex..<endIndex])
        }
    }
    
    
    extension Collection {
        var enumerated: Zip2Sequence<PartialRangeFrom<Int32>, Self> { zip(1..., self) }
    }
    
    
    
    
    class ViewController: UIViewController, UITextViewDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            createTextView()
        }
    
        func createTextView() {
            
            var textView: UITextView!
            var textStorage: NSTextStorage!
            // 1
            let attrs = [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body),  .foregroundColor: UIColor.label]
           
            let attrString = NSAttributedString(string: "Press the return key to add a new line.", attributes: attrs)
            textStorage = NSTextStorage()
            textStorage.append(attrString)
    
            let newTextViewRect = view.bounds
    
            // 2
            //let layoutManager = LineNumberLayoutManager()
            let layoutManager = LineNumberLayoutManager()
            // 3
            let containerSize = CGSize(width: newTextViewRect.width, height: .greatestFiniteMagnitude)
            let container = NSTextContainer(size: containerSize)
            container.widthTracksTextView = true
            layoutManager.addTextContainer(container)
            textStorage.addLayoutManager(layoutManager)
    
            // 4
            textView = UITextView(frame: newTextViewRect, textContainer: container)
            textView.delegate = self
           
            
            
            textView.isScrollEnabled = true
            textView.textContainerInset = .zero
            textView.textContainer.lineFragmentPadding = 9.5
            //textView.layoutManager.usesFontLeading = false
            textView.keyboardDismissMode = .interactive
            textView.textContainerInset = UIEdgeInsets(top: 50, left: 10, bottom: 0, right: 10)
           
            
           
            textView.typingAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17)]
            textView.textAlignment = NSTextAlignment.justified
            textView.allowsEditingTextAttributes = true
            textView.isSelectable = true
            textView.isUserInteractionEnabled = true
            
            
         
            textView.dataDetectorTypes = .all
            view.addSubview(textView)
    
            // 5
            textView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
              textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
              textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
              textView.topAnchor.constraint(equalTo: view.topAnchor),
              textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
          }
    
        
        
        class LineNumberLayoutManager: NSLayoutManager {
            
            let lineSize = CGSize(width: 8, height: 8)
            var lineColor = UIColor(red:1.00, green:1.00, blue:1.00, alpha:1.0)
            
         
    
            
             override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
                 var linenumber = 0
                              
                 
                 super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin)
                 guard let textStorage = self.textStorage else { return }
                 
                 enumerateLineFragments(forGlyphRange: glyphsToShow) { [self] (rect, usedRect, textContainer, glyphRange, _) in
                     
                     linenumber += 1
                     
let origin = CGPoint(x: 10, y: usedRect.origin.y + 48 + (usedRect.size.height - self.lineSize.height) / 2)
                     var newLineRange = NSRange(location: 0, length: 0)
                     if glyphRange.location > 0 {
                         
                         newLineRange.location = glyphRange.location - 1
                         newLineRange.length = 1
                          
                     }
                     var isNewLine = true
                     if newLineRange.length > 0 {
                         isNewLine = textStorage.string.substring(with: newLineRange) == "\n"
                         
                     }
                     if isNewLine {
                         
                         let str = textStorage.string.components(separatedBy: .newlines)
                         
                         "\(linenumber)\(str)".draw(in:CGRect(origin: origin, size: lineSize))
                         
                        
                  }
                     
        }
                 
             }
            
        }
        
    }

Upvotes: 1

Views: 684

Answers (0)

Related Questions