NSTextView 在使用不同的 NSParagraphStyles 更改属性时更新速度太慢
NSTextView is too slow to update while changing the attributes with different NSParagraphStyles
我希望 NSTextView 对象通过更改 NSParagraphStyle 间距来响应 Tab 键命中。它确实非常慢!事实上,如果我做这个更改太快(按 Tab 键太快),我最终会出现故障,有时甚至会导致崩溃。这是视频:https://drive.google.com/open?id=0B4aMQXnlIOvCUXNjWTVXVkR3NHc. And another one: 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
}
当我按 Tab 键时,Time Profiler 工具显示一个高峰。它说每次我按 Tab 键时,NSTextStorage attributesAtIndex 最多需要 40 毫秒。
我检查过:如果我删除 NSParagraphStyle 更改,一切都会变得正常。所以问题是:我应该如何更新段落样式?
嗯...在 Apple 文档或 Google 中都没有找到这样的解决方案...只是进行了实验,结果是我在 textView.display() 调用后添加 self.textStorage.setAttributes,一切正常!
更新:setNeedsDisplay(invalidRect) 做得更好,因为您可能只重绘文本视图可见矩形的脏部分
我希望 NSTextView 对象通过更改 NSParagraphStyle 间距来响应 Tab 键命中。它确实非常慢!事实上,如果我做这个更改太快(按 Tab 键太快),我最终会出现故障,有时甚至会导致崩溃。这是视频:https://drive.google.com/open?id=0B4aMQXnlIOvCUXNjWTVXVkR3NHc. And another one: 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
}
当我按 Tab 键时,Time Profiler 工具显示一个高峰。它说每次我按 Tab 键时,NSTextStorage attributesAtIndex 最多需要 40 毫秒。
我检查过:如果我删除 NSParagraphStyle 更改,一切都会变得正常。所以问题是:我应该如何更新段落样式?
嗯...在 Apple 文档或 Google 中都没有找到这样的解决方案...只是进行了实验,结果是我在 textView.display() 调用后添加 self.textStorage.setAttributes,一切正常!
更新:setNeedsDisplay(invalidRect) 做得更好,因为您可能只重绘文本视图可见矩形的脏部分