1

I'm having a custom NSLayoutManager with these two methods overwritten:

override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
    super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin)

    let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil)
    textStorage?.enumerateAttribute(.blur, in: characterRange, options: .longestEffectiveRangeNotRequired, using: { (value, subrange, _) in
        guard let key = value as? String, !key.isEmpty else { return }
        let blurGlyphRange = glyphRange(forCharacterRange: subrange, actualCharacterRange: nil)
        drawBlur(forGlyphRange: blurGlyphRange)
        textStorage?.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], range: blurGlyphRange)
    })
}

private func drawBlur(forGlyphRange tokenGlypeRange: NSRange) {
    guard let textContainer = textContainer(forGlyphAt: tokenGlypeRange.location, effectiveRange: nil) else { return }
    let withinRange = NSRange(location: NSNotFound, length: 0)
    enumerateEnclosingRects(forGlyphRange: tokenGlypeRange, withinSelectedGlyphRange: withinRange, in: textContainer) { (rect, _) in
        let blurRect = rect.offsetBy(dx: self.textContainerOriginOffset.width, dy: self.textContainerOriginOffset.height)
        UIColor.red.setFill()
        UIBezierPath(roundedRect: blurRect, cornerRadius: 4).fill()
    }

Everything works fine except when I set the UITextView isScrollingEnabled on false I enter an endless loop caused by the textStorage enumerateAttribute method in drawGlyphs.

I don't understand why this happens and also I don't know how to prevent this. Someone who knows more about this?

EDIT

If I remove the textStorage addAttributes with the foregroundColor then it works. So that's causing the loop for some reason.

user1007522
  • 7,858
  • 17
  • 69
  • 113
  • Why did you delete the earlier question? – matt Mar 16 '20 at 19:39
  • Sorry if it was not correct. But for me this is another problem. This works only the problem is with the endless loop when isScrollingEnabled is false. I thought if I edit again and again it will become polluted. – user1007522 Mar 16 '20 at 20:23
  • I understand but we were working out an answer to your question and you just deleted. Instead of letting me turn my comment into an answer and completing the Q&A cycle, you _used_ what my comment suggested but deleted the whole question so that it's impossible to get credit for it. It feels like theft. :) – matt Mar 16 '20 at 20:36
  • I'm so sorry. Can I somehow give you that credit? – user1007522 Mar 17 '20 at 07:50

1 Answers1

1

I found the problem why it comes in and endless loop. The textstorage updates/add the attribute and then notifies the layoutmanager again.

Solution is to create your own textstorage like this:

class CustomTextStorage: NSTextStorage {
  private let backingStore = NSMutableAttributedString()

  override var string: String {
    return backingStore.string
  }

  override init() {
    super.init()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key: Any] {
      return backingStore.attributes(at: location, effectiveRange: range)
  }

  override func replaceCharacters(in range: NSRange, with str: String) {
    beginEditing()
    backingStore.replaceCharacters(in: range, with:str)
    edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
    endEditing()
  }

  override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
    beginEditing()
    backingStore.setAttributes(attrs, range: range)
    if let attrs = attrs, let _ = attrs[.blur] {
        backingStore.addAttribute(.foregroundColor, value: UIColor.clear, range: range)
    }
    edited(.editedAttributes, range: range, changeInLength: 0)
    endEditing()
  }
}

and remove the line under the draw method in the layoutmanager:

textStorage?.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], range: blurGlyphRange)
user1007522
  • 7,858
  • 17
  • 69
  • 113