NSTokenField 未触发操作

NSTokenField not firing action

我有一个 NSTokenField 可以向对象(文档)添加标签。我想在将令牌添加到令牌字段时(键入令牌化字符时)用新标签更新对象。不幸的是,这似乎不起作用。 NSTokenField 连接到我的控制器中的一个动作,但从未调用此动作方法。

我也有一个NSTextField以同样的方式连接到控制器,它在控制器中的动作方法被调用。

我也尝试过观察键值:

- (void) awakeFromNib {
    [tokenField addObserver:self forKeyPath:@"objectValue" options:NSKeyValueObservingOptionNew context:NULL];
}


- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if([object isEqual:tokenField]){
        NSLog(@"Tokens changed");
    }
}

但只有当我以编程方式更改标记时才会调用此操作。

tokenField 中的令牌更改时如何通知我?

NSTokenField 动作选择器不会在创建新标签时调用。根据您在 Interface Builder 中进行的设置,它会在您按回车键结束编辑时调用(仅在输入时发送),或者当您以其他方式结束编辑时(发送结束编辑)。为了获得良好的控制,您需要另一种方法。


将标记化字符添加到标记字段时出现的蓝色 标签 称为文本附件(NSTextAttachment 的实例)。一种计算标记何时从标记字段中 added/removed 的方法是跟踪标记字段的基础属性字符串中包含的这些对象数量的变化。

要访问相关的属性字符串,您需要获取 fieldEditor 的 layoutManager - 最终提供出现在文本中的字符串的对象 -看法。收到后,每次收到 controlTextDidChange: 消息时,计算其 attributedStringstring 表示形式中的文本附件数量。如果本次统计的数字大于上次统计记录的数字,则刚刚添加了一个标签。

#import "AppDelegate.h"

@interface AppDelegate ()

@property (weak) IBOutlet NSWindow *window;
@property (weak) NSLayoutManager *lm;
@property (nonatomic) NSUInteger tokenCount;

@end

@implementation AppDelegate

// The text in the fieldEditor has changed. If the number of attachments in the
// layoutManager's attributedString has changed, either a new tag has been added,
// or an existing tag has been deleted.
-(void)controlTextDidChange:(NSNotification *)obj {
    NSUInteger updatedCount = [self countAttachmentsInAttributedString:self.lm.attributedString];
    if (updatedCount > self.tokenCount) {
        NSLog(@"ADDED");
        self.tokenCount = updatedCount;
    } else if (updatedCount < self.tokenCount) {
        NSLog(@"REMOVED");
        self.tokenCount = updatedCount;
    }
}

// About to start editing - get access to the fieldEditor's layoutManager
-(BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
    self.lm = [(NSTextView *)fieldEditor layoutManager];
    return YES;
}

// Iterate through the characters in an attributed string looking for occurrences of
// the NSAttachmentCharacter.
- (NSInteger)countAttachmentsInAttributedString:(NSAttributedString *)attributedString {
    NSString *string = [attributedString string];
    NSUInteger maxIndex = string.length - 1;
    NSUInteger counter = 0;

    for (int i = 0; i < maxIndex + 1; i++) {
        if ([string characterAtIndex:i] == NSAttachmentCharacter) {
            counter++;
        }
    }
    return counter;
}

@end

@paul-patterson 的代码移植到 Swift 3:

override func controlTextDidChange(_ obj: Notification) {
    guard let fieldEditor = self.tokenField.currentEditor() as? NSTextView,
        let layoutManager = fieldEditor.layoutManager
        else { return }

    func countAttachments(attributedString: NSAttributedString) -> Int {

        let string = attributedString.string as NSString
        let maxIndex = string.length - 1
        var counter = 0

        for i in 0..<maxIndex {
            if string.character(at: i) == unichar(NSAttachmentCharacter) {
                counter += 1
            }
        }

        return counter
    }

    let currentCount = countAttachments(attributedString: layoutManager.attributedString())
    // cache count or act on it directly
}

奇怪的是,以下 没有 产生 Swift 中的预期结果:

layoutManager.attributedString().string
    .split(by: Character(UnicodeScalar(NSAttachmentCharacter)!)).count

相反,它 returns 当用户没有输入时为 0,当正在编辑令牌时为 1。

let isEditing = layoutManager.attributedString().string
    .split(by: Character(UnicodeScalar(NSAttachmentCharacter)!)).count == 1

结合使用这两种方法,您可以使用状态机编写自定义 "did add/remove token" 回调。 (不过,我认为这不是一种非常安全的实现方式。)

  • 使用 countAttachments(attributedString:) 跟踪令牌数。
  • 使用isEditing检查...
    1. 如果用户开始添加新笔记(新计数 > 旧计数 && isEditing == true)
    2. 如果用户开始编辑现有笔记(新计数 == 旧计数 && isEditing == true)
    3. 如果用户完成了一个标记(oldIsEditing == true && newIsEditing == false)