自定义 NSTextView insert文本:替换范围中断拼写检查



我有一个自定义的NSTextView子类,还有一个自定义的NSTextStorage组件。 NSTextStorage 根据上下文修改用户输入的文本。

由于最终文本可能会比用户最初输入的文本短,因此我不得不在 NSTextView 中覆盖insertText:replacementRange。 一个最小的例子是:

- (void) insertText:(id)string replacementRange:(NSRange)replacementRange {
if ([self hasMarkedText]) {
[[self textStorage] replaceCharactersInRange:[self markedRange] withString:string];
} else {
[[self textStorage] replaceCharactersInRange:[self selectedRange] withString:string];
}
[self didChangeText];
}

这在几个月的广泛测试中工作正常。除了禁用自动拼写检查和更正。 "波浪线"不会出现在拼写错误的单词下,除非我停止键入、移动鼠标并在我的应用程序之间切换焦点。 几秒钟后,整个文本视图将被拼写检查。 因为它发生在事后,所以自动更正当然是禁用的。

如果我禁用自定义insertText:replacementRange:方法,其他一切正常,并且自动拼写功能将返回。 我只需要注意不要触发导致文本缩短的更改,因为它会触发属性超出范围错误(首先是我的自定义方法的原始原因。

显然,苹果对insertText:replacementRange:的实施比我的要多得多。 我已经尝试了多种变体[self checkTextInRange...][self checkTextInSelection:]等。 它们都无法恢复正常功能。

搜索Apple的文档无助于指出我从方法中遗漏的内容,这些内容导致拼写检查中断。 任何指示或想法将不胜感激!!

提前感谢!

编辑:以下是我的NSTextStorage提供的各种行为的一些示例。(|表示插入插入符号(

从以下开始:

* item
* |

如果我按下返回键,我最终会得到以下内容(删除*<space>(:

* item
|

另一个示例,如果启用了"更改跟踪":

this is thee| time

如果我点击删除:

this is the|{--e--} time

如您所见,一次击键可能会导致在文本中添加或删除多个字符。

编辑 2:仅供参考 - 当在文档末尾按回车键时发生缩短时,我遇到的属性超出范围的问题 - NSTextview 尝试设置新的段落样式,只是发现文档比预期的短。 我找不到更改范围NSTextview目标的方法。

我有一个部分解决方案。

在我的自定义insertText:replacementRange:方法中,在didChangeText之前:

NSinteger wordCount;
NSOrthography * orthography;
static NSInteger theWordCount;
NSOrthography  * orthography;
NSRange spellingRange = <range to check>
NSArray * results = [[NSSpellChecker sharedSpellChecker] checkString:[[self textStorage] string]
range:spellingRange
types:[self enabledTextCheckingTypes]
options:NULL
inSpellDocumentWithTag:0
orthography:&orthography
wordCount:&theWordCount];
if (results.count) {
[self handleTextCheckingResults:results forRange:spellingRange types:[self enabledTextCheckingTypes] options:@{} orthography:orthography wordCount:theWordCount];
}

但是,这是不完整的:

  • 拼写检查和语法检查工作正常
  • 自动拼写更正和文本替换不起作用(即使启用(

(已编辑 2018-05-30(

最新回复 (2018-05-22(:

这个问题又抬起了丑陋的头,我真的需要弄清楚。

  1. 我的自定义 NSTextStorage 与描述的基本相同,并且仍然有效。

  2. 我在NSTextView上使用自定义insertText:replacementRange:,但它调用[super insertText:replacementRange:]以利用Apple的幕后工作,使拼写等工作更好。 我的自定义方法只需要设置一个布尔值。

  3. 在缩短文本时,我仍然收到AppleinsertText:replacementRange:的请求,要求文本中不存在的部分的属性。 以前,我会卡在这里,因为我尝试的所有内容要么导致崩溃,要么导致 Apple 的代码无限期地重复请求不存在的属性。

  4. 最后,我尝试使用 NULL 范围指针返回假属性,这似乎让 Apple 的代码很高兴:

    - (NSDictionary *) attributesAtIndex:(NSUInteger)location effectiveRange:(nullable NSRangePointer)range {
    if (location > _backingAttributes.length) {
    // This happens if we shrink the text before the textview is aware of it.
    // For example, if we expand "longtext" -> "short" in our smart string, then
    // The textview may set and request attributes past the end of our
    // _backing string.
    // Initially this was due to error in my code, but now I had to add
    // This error checking back
    NSLog(@"get attributes at (%lu) in (%lu)", (unsigned long)location, (unsigned long)_backingAttributes.length);
    NSLog(@"error");
    // Apparently returning fake attributes satisfies [NSTextView insertText:replacementRange:]
    range = NULL;
    return  @{
    NSForegroundColorAttributeName : [BIColor redColor],
    NSFontAttributeName : [BIFont fontWithName:@"Helvetica" size:14.0]
    };
    } else {
    return [_backingAttributes attributesAtIndex:location effectiveRange:range];
    }
    }
    
  5. 经过进一步的测试,结果证明这还不够。 我最终将以下内容添加到设置器中,以存储macOS尝试设置的无效属性和范围:

    - (void) setAttributes:(NSDictionary<NSString *,id> *)attrs range:(NSRange)range {
    if (NSMaxRange(range) > _backingAttributes.length) {
    _invalidAttrs = attrs;
    _invalidRange = range;
    } else {
    [self beginEditing];
    [_backingAttributes setAttributes:attrs range:range];
    [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
    [self endEditing];
    }
    }
    
  6. 我更新了 'attributesAtIndex:effectiveRange: 以在使用无效范围调用时返回以下内容,而不是返回上面的假属性:

    // Apparently returning fake attributes satisfies [NSTextView insertText]
    *range = _invalidRange;
    return _invalidAttrs;
    

这似乎在以前会触发异常或无限循环的各种条件下工作。

最新更新