【问题标题】:Custom NSTextView insertText:replacementRange breaks Spell Checking自定义 NSTextView insertText:replacementRange 中断拼写检查
【发布时间】:2017-08-16 05:02:31
【问题描述】:

我有一个自定义的 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: 方法,其他一切正常,并且自动拼写功能返回。我只需要小心不要触发导致缩短文本的更改,因为它会触发属性超出范围错误(首先是我的自定义方法的原始原因。)

显然,Apple 对insertText:replacementRange: 的实现比我的要多得多。我在[self checkTextInRange...][self checkTextInSelection:] 等上尝试了多种变体。它们都没有恢复正常的功能。

搜索 Apple 的文档并不能帮助我指出我的方法中遗漏的导致拼写检查中断的内容。任何指针或想法将不胜感激!

提前致谢!

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

开始于:

* item
* |

如果我按回车键,我会得到以下结果(删除*<space>):

* item
|

另一个例子,如果启用“更改跟踪”:

this is thee| time

如果我点击删除:

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

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

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

【问题讨论】:

  • 如何修改文本存储?你打电话给beginEditingendEditingedited:range:changeInLength: 什么的?
  • 是的——所有三个。 NSTextStorage 中的所有内容都可以完美运行(至少在我几个月和几个月的使用期间)。我能够找到的唯一问题是拼写问题,如上已修复 - 禁用自定义 insertText:replacementRange:(但仍使用我的自定义 NSTextStorage)并返回拼写。
  • 显然您无法通过替换 insertText:replacementRange: 来修复属性超出范围错误。错误来自哪里?
  • 键入时的一种可能行为是,按回车键实际上可以删除插入点之前的字符,导致字符串比用户按回车时短。在 Apple 的 insertText... 方法中的某个时刻,它会从 NSTextStorage 请求属性,期望字符串比实际更长。我尝试发回虚假属性,但这使问题更加复杂。通过覆盖该方法,我遗漏了损坏的部分,但显然也遗漏了我想要的东西.... ;)
  • NSTextStorageNSTextStorageDelegate的哪些方法中修改文字?

标签: objective-c macos cocoa spell-checking nstextview


【解决方案1】:

我有一个部分解决方案。

在我自定义的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];
}

但是,这是不完整的:

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

【讨论】:

  • 我意识到我忘了更新这个。我能够让事情正常进行(至少 99% 的时间)。启用语法检查需要扩展检查范围以包括整个句子。我还必须更加严格和明确地检查和设置不同点的检查类型,而不是依赖于`[self enabledTextCheckingTypes]。简而言之,它需要大量的箍跳,仍然不完美。
  • 我终于能够解决需要解决方法的原始问题。所以我不再需要自定义insertText:replacementRange:。这意味着大大提高了拼写检查的性能。因此,如果您能提供帮助,我强烈建议您避开这条路线。 ;)
【解决方案2】:

(2018-05-30 编辑)

更新回复(2018-05-22):

这个问题又浮出水面,我真的需要弄清楚。

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

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

  3. 在缩短文本时,我仍然收到 Apple 的 insertText: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. 经过进一步测试,结果证明这还不够。我最终将以下内容添加到 setter 以存储 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;
    

这似乎适用于以前会触发异常或无限循环的各种条件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多