NSTokenField tokenField: завершений ForSubstring:indexOfToken:indexOfSelectedItem: indexOfToken всегда ноль

Я попробовал простой первый пример NSTokenField с приложением, использующим ARC на основе документов, в результате чего мой класс Document стал NSTokenFieldDelegate. Это работает, но с одной стороны: метод делегата tokenField: завершений ForSubstring:indexOfToken:indexOfSelectedItem: никогда не видит ничего, кроме 0 для indexOfToken, даже когда я успешно редактирую токен, который не является первым в строке токенов. Я использую XCode 4.5 на OS X 10.8.2 с платформой 10.8.

Вопрос: почему всегда 0? Я ожидаю, что это будет индекс токена в косвенно видимом массиве токенов 0 .. n - 1 в редактируемом пользователем поле.

Для воспроизведения запустите проект, как указано выше, и добавьте текст ниже, затем используйте редактор XIB и перетащите NSTokenField в окно документа, установите поле токена в качестве tokenField документа и сделайте экземпляр документа делегатом поля токена.

Document.h:

#import <Cocoa/Cocoa.h>

@interface Document : NSDocument <NSTokenFieldDelegate>
{
    IBOutlet NSTokenField *tokenField;  // of (Token *).
    NSMutableDictionary *tokens;        // of (Token *).
}
@end

Token.h:

#import <Foundation/Foundation.h>

@interface Token : NSObject
@property (strong, nonatomic) NSString *spelling;
- (id)initWithSpelling:(NSString *)s;
@end

Token.m:

#import "Token.h"

@implementation Token
@synthesize spelling;

- (id)initWithSpelling:(NSString *)s
{
    self = [super init];
    if (self)
        spelling = s;
    return self;
}

@end

Document.m:

#import "Document.h"
#import "Token.h"

@implementation Document

- (id)init
{
    self = [super init];
    if (self) {
        tokens = [NSMutableDictionary dictionary];
    }
    return self;
}

...

#pragma mark NSTokenFieldDelegate methods

- (NSArray *)tokenField:(NSTokenField *)tokenField
completionsForSubstring:(NSString *)substring
           indexOfToken:(NSInteger)tokenIndex
    indexOfSelectedItem:(NSInteger *)selectedIndex
{
    NSLog(@"tokenField:completionsForSubstring:\"%@\" indexOfToken:%ld indexOfSelectedItem:",
          substring, tokenIndex);
    NSMutableArray *result = [NSMutableArray array];
    for (NSString *key in tokens) {
        //NSLog(@"match? \"%@\"", key);
        if ([key hasPrefix:substring])
            [result addObject:key];
    }
    return result;
}

- (id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString
{
    NSLog(@"tokenField:representedObjectForEditingString:\"%@\"", editingString);
    Token *token;
    if ((token = [tokens objectForKey:editingString]) == nil) {
        token = [[Token alloc] initWithSpelling:editingString];
        [tokens setObject:token forKey:editingString];
        //NSLog(@"token %@", [token description]);
        NSLog(@"tokens %@", [tokens description]);
    }
    return token;
}

- (NSString *)tokenField:(NSTokenField *)tokenField displayStringForRepresentedObject:(id)representedObject
{
    NSString *spelling = [representedObject spelling];
    NSLog(@"tokenField:displayStringForRepresentedObject: = \"%@\"", spelling);
    return spelling;
}

@end

Ввод токенов завершается символом новой строки или запятой.

2 ответа

Это определенно похоже на то, что вы должны сообщить в Apple.

Чтобы определить индекс отредактированного токена в NSTokenField Я впервые сделал подкласс NSTextView так что у меня есть редактор пользовательских полей. (Если вы идете по этому маршруту, не забудьте настроить свой NSTextView экземпляр, чтобы быть полевым редактором с -[NSTextView setFieldEditor:].) Затем я подкласс NSTokenFieldCell переопределяя -[NSCell fieldEditorForView:] в

  1. создать экземпляр моего NSTextView подкласс,
  2. установить это NSTextView экземпляр будет self делегат, и
  3. вернуть это NSTextView пример.

Воплощать в жизнь tokenFieldCell:completionsForSubstring:indexOfToken:indexOfSelectedItem: в вашем NSTextView подкласс:

- (NSArray *)tokenFieldCell:(NSTokenFieldCell *)tokenFieldCell completionsForSubstring:(NSString *)substring indexOfToken:(NSInteger)tokenIndex indexOfSelectedItem:(NSInteger *)selectedIndex
{
    // The tokenIndex passed to this function seems to be 0 in all cases, so we
    // need to determine the tokenIndex ourselves. The range returned by
    // NSText's selectedRange method treats non-plain-text tokens as if they
    // have unit length. So, for each token, subtract from the range location
    // either the token's length if it's a plain text token, or 1 if it's any
    // other style of token. Each time we subtract from the range location,
    // increment tokenIndex. When the range location becomes less than or equal
    // to 0, tokenIndex will be the index of the edited token.
    tokenIndex = 0;
    NSInteger rangeLocation = self.selectedRange.location;
    for (id token in tokenFieldCell.objectValue) {
        if ([self tokenFieldCell:tokenFieldCell styleForRepresentedObject:token] == NSPlainTextTokenStyle) {
            rangeLocation -= [self tokenFieldCell:tokenFieldCell displayStringForRepresentedObject:token].length;
        } else {
            rangeLocation--;
        }
        if (rangeLocation > 0) {
            tokenIndex++;
        } else {
            break;
        }
    }
}

Идея здесь состоит в том, чтобы использовать тот факт, что selectedRange из NSTextView рассчитывается исходя из предположения, что токены непростого текста имеют длину 1. Вычитая длины токенов из selectedRange местоположение, пока местоположение не является отрицательным, мы можем определить индекс токена.

Обратите внимание, что ваш NSTextView Подкласс должен также реализовать tokenFieldCell:displayStringForRepresentedObject: а также tokenFieldCell:styleForRepresentedObject: чтобы это работало.

Вот мое решение в Swift, которое работает хорошо для меня. Просто добавьте это к вашему viewController,

     func tokenIndex() -> Int {
        var index = 0
        let range = self.tokenField?.currentEditor()?.selectedRange
        let rangeLocation = range?.location ?? 0

        let string = self.tokenField?.currentEditor()?.string as NSString?
        if let subString = string?.substring(to: rangeLocation) as NSString? {
            let maxLimit = subString.length

            for i in 0..<maxLimit {
                //each token is represented as NSAttachmentCharacter,
                //so count it till current selected range
                if subString.character(at: i) == unichar(NSAttachmentCharacter) {
                    index += 1
                }
            }
        }

        return index
    }
Другие вопросы по тегам