将文本添加到特殊格式的NSTextField,而不格式化该文本

时间:2016-03-04 19:46:31

标签: objective-c macos cocoa

我需要编写NSTextField代码,它基本上与Spotlight搜索中的计算器功能相似。我已成功使用NSExpression解析实际的输入表达式并计算结果,但它是我坚持使用的视图部分。

如何在用户输入时在文本字段中呈现答案,但不将其作为输入的一部分?我是否可能必须继承NSTextField?

只是创建一个NSFormatter并没有给我提供我需要的所有功能。我可以使用委托的controlTextDidEndEditing:消息立即计算并用结果替换文本字段,但右边的动态总和往返于格式化程序。

有没有人知道一个接近我想做的事情的例子?

我对Cocoa很陌生,并且在知道要实现这一目标的哪些组件方面遇到了一些麻烦。

2 个答案:

答案 0 :(得分:2)

首先,使用NSTextView代替NSTextField会更简单(我认为),因为使用NSTextField你会进入“字段编辑器”疯狂,其中文本字段在具有焦点时使用封面下的文本视图(字段编辑器)。我们需要使用文本视图委托方法来实现它,所以我们只使用NSTextView

基本思想是我们将使用属性字符串以大黑色字符显示公式,然后使用较小的灰色字符显示答案。每当用户更改输入时,我们将删除旧答案并附加新答案。

对于玩具实施,我们只会在app委托中执行所有操作。我们有一个连接到XIB中文本视图的插座:

@interface AppDelegate () <NSTextDelegate, NSTextViewDelegate>

@property (weak) IBOutlet NSWindow *window;
@property (strong) IBOutlet NSTextView *textView;

@end

在XIB中,文本视图的delegate出口连接到应用代理。

我们还需要存储答案的长度(所以我们可以在添加新答案之前将其删除)以及公式和答案的文本属性:

@implementation AppDelegate {
    NSUInteger answerLength;
    NSDictionary *formulaAttributes;
    NSDictionary *answerAttributes;
}

- (void)awakeFromNib {
    [super awakeFromNib];

    formulaAttributes = @{
                          NSFontAttributeName: [NSFont systemFontOfSize:32 weight:NSFontWeightLight]
                          };

    answerAttributes = @{
                         NSFontAttributeName: [NSFont systemFontOfSize:24 weight:NSFontWeightLight],
                         NSForegroundColorAttributeName: [NSColor grayColor]
                         };

    [self updateAnswerInTextView];
}

每当文本视图的内容发生变化时,我们都希望删除旧答案并附加新答案。我们还需要在修改文本之前保存所选范围,并在之后恢复它,因为文本视图在我们修改它时将插入点放在文本的末尾。最后一件事:用户可以将属性文本粘贴到文本视图中,我们希望删除该粘贴文本上的任何属性。因此,我们在删除旧答案后重置文本的属性。我们使用NSTextDelegate方法完成所有这些操作:

- (void)textDidChange:(NSNotification *)notification {
    [self updateAnswerInTextView];
}

- (void)updateAnswerInTextView {
    NSRange selectedRange = self.textView.selectedRange;
    [self removeAnswerFromTextView];
    [self applyFormulaAttributesToTextView];
    [self appendAnswerToTextView];
    self.textView.selectedRange = selectedRange;
}

以下是辅助方法:

- (void)removeAnswerFromTextView {
    NSTextStorage *storage = self.textView.textStorage;
    [storage replaceCharactersInRange:self.answerRange withString:@""];
    answerLength = 0;
}

- (void)applyFormulaAttributesToTextView {
    NSTextStorage *storage = self.textView.textStorage;
    [storage setAttributes:formulaAttributes range:NSMakeRange(0, storage.length)];
}

- (void)appendAnswerToTextView {
    id answer = [self answer];
    NSString *answerString = [NSString stringWithFormat:@" = %@", answer];
    NSAttributedString *richAnswer = [[NSAttributedString alloc] initWithString:answerString attributes:answerAttributes];
    [self.textView.textStorage appendAttributedString:richAnswer];
    answerLength = answerString.length;
}

- (NSRange)answerRange {
    NSTextStorage *storage = self.textView.textStorage;
    return NSMakeRange(storage.length - answerLength, answerLength);
}

- (id)answer {
    @try {
        NSExpression *expression = [NSExpression expressionWithFormat:self.textView.textStorage.string];
        return [expression expressionValueWithObject:nil context:nil];
    }
    @catch (NSException *exception) {
        return @"???";
    }
}

请注意NSExpression对此不是一个好的数学解析器,原因有两个:

  • 如果出现解析错误,它会抛出一个异常,并且Objective-C在异常后并没有真正清理,所以可能会有内存泄漏或更糟。
  • 它使用%来表示格式说明符,这在用户输入的字符串中并不是一个好主意。

您应该考虑使用the old Objective-C version of DDMathParser或其他一些库。

无论如何,上面的代码评估用户的公式并显示答案,但它有一个问题:用户可以选择和修改字符串的答案部分。要解决这个问题,我们需要实现一个NSTextViewDelegate方法,将所选范围限制为文本的公式部分:

- (NSRange)textView:(NSTextView *)textView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange {
    NSUInteger answerStart = textView.textStorage.length - answerLength;
    NSUInteger newSelectedEnd = NSMaxRange(newSelectedCharRange);
    if (newSelectedEnd > answerStart) {
        newSelectedEnd = answerStart;
        NSUInteger newSelectedStart = MIN(newSelectedCharRange.location, newSelectedEnd);
        newSelectedCharRange.location = newSelectedStart;
        newSelectedCharRange.length = newSelectedEnd - newSelectedStart;
    }
    return newSelectedCharRange;
}

答案 1 :(得分:0)

您可以使用key-value validation执行此操作。这使您有机会更改用户输入而不是简单地拒绝它。你应该打开&#34;立即验证&#34;或者对于文本字段。

您可以使用属性识别添加的结果文本,并在每次更改时替换它们。

只是一个想法。

相关问题