NSTextFinder +以编程方式更改NSTextView中的文本

时间:2016-02-08 16:08:01

标签: cocoa nstextview

我有一个NSTextView,我想使用查找栏。文本是可选的,但不可编辑。我以编程方式更改了文本视图中的文本。

当NSTextFinder在文本更改后尝试选择下一个匹配项时,此设置可能会崩溃。似乎NSTextFinder坚持过时的增量匹配范围。

我尝试了几种更改文本的方法:

[textView setString:@""];

NSTextStorage *newStorage = [[NSTextStorage alloc] initWithString:@""];

[textView.layoutManager replaceTextStorage:newStorage];

[textView.textStorage beginEditing];
[textView.textStorage setAttributedString:[[NSAttributedString alloc] initWithString:@""]];
[textView.textStorage endEditing];

只有replaceTextStorage:调用 - [NSTextFinder noteClientStringWillChange]。以上都不调用 - [NSTextFinder cancelFindIndicator]。

即使NSTextFinder收到有关文本更改的通知,它也会在Find Next(命令-G)上崩溃。

我还尝试将自己的NSTextFinder实例创建为suggested in this post。尽管NSTextView没有实现NSTextFinderClient协议,但这种方法与没有NSTextFinder实例的情况一样有效。

在NSTextView中使用NSTextFinder的正确方法是什么?

2 个答案:

答案 0 :(得分:2)

我的应用程序中的文本视图遇到了同样的问题,更令人烦恼的是,您在互联网上找到的所有“解决方案”都是不正确的,或者至少是不完整的。所以这是我的贡献。

在NSTextView中设置textView.useFindBar = YES时,此文本视图会在内部创建NSTextFinder,并将搜索/替换命令转发给它。不幸的是,NSTextView似乎无法正确处理您以编程方式对其关联的NSTextStorage所做的更改,这会导致您提到的崩溃。

如果要更改此行为,创建私有NSTextFinder是不够的:您还需要避免使用其默认文本查找器的文本视图,否则将发生冲突并且新的文本查找器不会很多用途。

为此,您必须继承NSTextView:

@interface MyTextView : NSTextView

- (void) resetTextFinder; // A method to reset the view's text finder when you change the text storage

@end

在文本视图中,您必须覆盖用于控制文本查找器的响应方法:

@interface MyTextView ()  <NSTextFinderClient>
{
    NSTextFinder* _textFinder; // define your own text finder
}

@property (readonly) NSTextFinder* textFinder;

@end

@implementation MyTextView

// Text finder command validation (could also be done in method validateUserInterfaceItem: if you prefer) 

- (BOOL) validateMenuItem:(NSMenuItem *)menuItem
{
    BOOL isValidItem = NO;

    if (menuItem.action == @selector(performTextFinderAction:)) {
        isValidItem = [self.textFinder validateAction:menuItem.tag];
    }
    // validate other menu items if needed
    // ...
    // and don't forget to call the superclass
    else {
        isValidItem = [super validateMenuItem:menuItem];
    }

    return isValidItem;
}

// Text Finder

- (NSTextFinder*) textFinder
{
    // Create the text finder on demand
    if (_textFinder == nil) {
        _textFinder = [[NSTextFinder alloc] init];
        _textFinder.client = self;
        _textFinder.findBarContainer = [self enclosingScrollView];
        _textFinder.incrementalSearchingEnabled = YES;
        _textFinder.incrementalSearchingShouldDimContentView = YES;
    }

    return _textFinder;
}

- (void) resetTextFinder
{
    if  (_textFinder != nil) {
        // Hide the text finder 
        [_textFinder cancelFindIndicator];
        [_textFinder performAction:NSTextFinderActionHideFindInterface];

        // Clear its client and container properties
        _textFinder.client = nil;
        _textFinder.findBarContainer = nil;

        // And delete it
        _textFinder = nil;
    }
}

// This is where the commands are actually sent to the text finder
- (void) performTextFinderAction:(id<NSValidatedUserInterfaceItem>)sender
{
    [self.textFinder performAction:sender.tag];
}

@end

在文字视图中,您仍需要将属性usesFindBarincrementalSearchingEnabled设置为YES

在更改视图的文本存储(或文本存储内容)之前,您只需要在下次搜索时调用[myTextView resetTextFinder];为新内容重新创建全新的文本查找器。

如果您想了解有关NSTextFinder的更多信息,我见过的最佳文档位于AppKit Release Notes for OS X 10.7

答案 1 :(得分:1)

我提出的解决方案似乎与@jlj提供的解决方案非常相似。在两种解决方案中,NSTextView都用作NSTextFinder的客户端。

似乎主要区别在于我没有隐藏文本更改的查找栏。我还抓住了我的NSTextFinder实例。为此,我需要致电[textFinder noteClientStringWillChange]

更改文字:

NSTextView *textView = self.textView;
NSTextFinder *textFinder = self.textFinder;

[textFinder cancelFindIndicator];
[textFinder noteClientStringWillChange];

[textView setString:@"New text"];

视图控制器代码的其余部分如下所示:

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSTextFinder *textFinder = [[NSTextFinder alloc] init];

    [textFinder setClient:(id < NSTextFinderClient >)textView];
    [textFinder setFindBarContainer:[textView enclosingScrollView]];

    [textView setUsesFindBar:YES];
    [textView setIncrementalSearchingEnabled:YES];

    self.textFinder = textFinder;
}

- (void)viewWillDisappear
{
    NSTextFinder *textFinder = self.textFinder;

    [textFinder cancelFindIndicator];

    [super viewWillDisappear];
}

- (id)supplementalTargetForAction:(SEL)action sender:(id)sender
{
    id target = [super supplementalTargetForAction:action sender:sender];

    if (target != nil) {
        return target;
    }

    if (action == @selector(performTextFinderAction:)) {
        target = self.textView;

        if (![target respondsToSelector:action]) {
            target = [target supplementalTargetForAction:action sender:sender];
        }

        if ((target != self) && [target respondsToSelector:action]) {
            return target;
        }
    }

    return nil;
}