NSTextView слишком медленно обновляется при изменении атрибутов с разными NSPagraphStyles

Я хочу, чтобы объект NSTextView реагировал на нажатие клавиши Tab, изменяя интервал NSParagraphStyle. И это ОЧЕНЬ медленно!!! На самом деле, если я делаю эти изменения слишком быстро (слишком быстро нажимаю клавишу Tab), у меня в конечном итоге возникают сбои, которые иногда даже приводят к сбою. Вот видео: https://drive.google.com/open?id=0B4aMQXnlIOvCUXNjWTVXVkR3NHc. . И еще один: https://drive.google.com/open?id=0B4aMQXnlIOvCUDJjSEN0bFdqQXc.

Фрагмент кода из моего подкласса NSTextStorage:

override func attributesAtIndex(index: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject] {
    return storage.attributesAtIndex(index, effectiveRange: range)
}


override func replaceCharactersInRange(range: NSRange, withString str: String) {
    let delta = str.characters.count - range.length
    beginEditing()
    storage.replaceCharactersInRange(range, withString:str)
    edited([.EditedCharacters, .EditedAttributes], range: range, changeInLength: delta)
    endEditing()
}


override func setAttributes(attrs: [String : AnyObject]!, range: NSRange) {
    beginEditing()
    storage.setAttributes(attrs, range: range)
    edited(.EditedAttributes, range: range, changeInLength: 0)
    endEditing()
}

Фрагмент кода из моего подкласса NSTextView:

override func shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) -> Bool {
    super.shouldChangeTextInRange(affectedCharRange, replacementString: replacementString)
    guard replacementString != nil else { return true }

    if replacementString == "\t" {
        defer {
            let selectedRangesValues = self.selectedRanges
            var selectedRanges = [NSRange]()
            for value in selectedRangesValues {
                selectedRanges.append(value.rangeValue)
            }
            textController.switchToAltAttributesInRange(selectedRanges)
        }
        return false
    }
    return true
}

Фрагмент кода из моего TextController, который создает и применяет альтернативные атрибуты:

func switchToAltAttributesInRange(ranges : [NSRange]) {
    // get paragraph indexes from the ranges
    var indexes = [Int]()
    for range in ranges {
        for idx in textStorage.paragraphsInRange(range) {
            indexes.append(idx)
        }
    }

    // change attributes for all the paragraphs in those ranges
    for index in indexes {
        let paragraphRange = textStorage.paragraphRangeAtIndex(index)
        let element = elementAtIndex(index)
        let altElementType = altElementTypeForElementType(element.type)

        // change the attributes
        let newAttributes = paragraphAttributesForElement(type: altElementType.rawValue)
        self.textStorage.beginUpdates()
        self.textStorage.setAttributes(newAttributes, range: paragraphRange)
        self.textStorage.endUpdates()
    }
}

func paragraphAttributesForElement(type typeString: String) -> [String : AnyObject] {
    let elementPreset = elementPresetForType(elementType)

    // set font
    let font = NSFont (name: elementPreset.font, size: CGFloat(elementPreset.fontSize))!

    // set attributes
    let elementAttributes = [NSFontAttributeName: font,
                  NSParagraphStyleAttributeName : paragraphStyleForElementPreset(elementPreset, font: font),
                  NSForegroundColorAttributeName: NSColor.colorFromHexValue(elementPreset.color),
                  NSUnderlineStyleAttributeName : elementPreset.underlineStyle,
              ElementAttribute.AllCaps.rawValue : elementPreset.allCaps]

    return elementAttributes
}

func paragraphStyleForElementPreset(elementPreset : TextElementPreset, font : NSFont) -> NSParagraphStyle {
    let sceneParagraphStyle = NSMutableParagraphStyle()

    let spacing = elementPreset.spacing
    let spacingBefore = elementPreset.spacingBefore
    let headIndent = elementPreset.headIndent
    let tailIndent = elementPreset.tailIndent

    let cFont = CTFontCreateWithName(font.fontName, font.pointSize, nil)
    let fontHeight = CTFontGetDescent(cFont) + CTFontGetAscent(cFont) + CTFontGetLeading(cFont)

    sceneParagraphStyle.paragraphSpacingBefore = CGFloat(spacingBefore)
    sceneParagraphStyle.paragraphSpacing = fontHeight * CGFloat(spacing)
    sceneParagraphStyle.headIndent = ScreenMetrics.pointsFromInches(CGFloat(headIndent))
    sceneParagraphStyle.tailIndent = ScreenMetrics.pointsFromInches(CGFloat(tailIndent))
    sceneParagraphStyle.firstLineHeadIndent = sceneParagraphStyle.headIndent
    sceneParagraphStyle.lineBreakMode = .ByWordWrapping
    return sceneParagraphStyle
}

Инструмент Time Profiler показывает высокий пик, когда я нажимаю клавишу Tab. В нем говорится, что атрибуты NSTextStorageAtIndex занимают до 40 мс каждый раз, когда я нажимаю клавишу Tab.

Проверил: если убрать изменения NSParagraphStyle, все становится нормально. Итак, вопрос: как мне обновить стили абзаца?


person Vitalii Vashchenko    schedule 24.04.2016    source источник


Ответы (1)


Хм... Не нашел такого решения ни в Apple docs, ни в Google... Просто поэкспериментировал и оказалось, что если я добавляю textView.display() после вызова self.textStorage.setAttributes, то все работает нормально!

ОБНОВЛЕНИЕ: setNeedsDisplay(invalidRect) работает намного лучше, потому что вы можете перерисовать только грязную часть видимого прямоугольника текстового представления.

person Vitalii Vashchenko    schedule 25.04.2016
comment
Итак, ваш NSTextView теперь не медленный? Я также сталкиваюсь с той же проблемой в моем проекте iOS. Я предполагаю, что отрисовка текста может сильно потреблять процессор, и следует избегать ненужного рисования. - person ilovecomputer; 25.04.2016
comment
Это все еще слишком медленно. Но ручная перерисовка скрывает это от глаз пользователя. Я действительно не знаю, что еще я могу сделать. - person Vitalii Vashchenko; 25.04.2016
comment
Но, насколько я знаю, те изменения, которые я делаю в NSTextView, требуют, чтобы NSLayoutManager все равно перерисовывал все глифы. Так что, я думаю, не имеет значения, заставляю ли я вручную перерисовывать их или нет. - person Vitalii Vashchenko; 25.04.2016