我有一个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的正确方法是什么?
答案 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
在文字视图中,您仍需要将属性usesFindBar
和incrementalSearchingEnabled
设置为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;
}