Как проверить цепочку респондента? - PullRequest
20 голосов
/ 22 ноября 2010

Я делаю несколько сумасшедших документов в одном окне с архитектурой на основе документов, и я на 95% готов.

У меня есть эта двухуровневая архитектура документов, где родительский документ открывает и настраивает окно, предоставляя список «дочерних» документов. Когда пользователь выбирает одного из дочерних элементов, этот документ открывается с помощью того же оконного контроллера, и он помещает NSTextView в окно. Ассоциация документа контроллера окна изменяется так, что «редактируемая точка» и заголовок окна отслеживают текущий выбранный документ. Подумайте о проекте Xcode и о том, что происходит, когда вы редактируете в нем разные файлы.

Чтобы поместить код в псевдоформу, такой метод вызывается в родительском документе при открытии дочернего документа.

-(void)openChildDocumentWithURL:(NSURL *)documentURL {
  // Don't open the same document multiple times
  NSDocument *childDocument = [documentMapTable objectForKey:documentURL];
  if (childDocument == nil) {
    childDocument = [[[MyDocument alloc] init] autorelease];
    // Use the same window controller
    // (not as bad as it looks, AppKit swaps the window's document association for us)
    [childDocument addWindowController:myWindowController];
    [childDocument readFromURL:documentURL ofType:@"Whatever" error:NULL];

    // Cache the document
    [documentMapTable setObject:childDocument forKey:documentURL];
  }

  // Make sure the window controller gets the document-association swapped if the doc came from our cache
  [myWindowController setDocument:childDocument];

  // Swap the text views in
  NSTextView *currentTextView = myCurrentTextView;
  NSTextView *newTextView = [childDocument textView];
  [newTextView setFrame:[currentTextView frame]]; // Don't flicker      

  [splitView replaceSubview:currentTextView with:newTextView];

  if (currentTextView != newTextView) {
    [currentTextView release];
    currentTextView = [newTextView retain];
  }
}

Это работает, и я знаю, что контроллер окна имеет правильную ассоциацию документа в любой момент времени, так как точка изменения и заголовок следуют за любым документом, который я редактирую.

Однако, когда я нажимаю сохранить, (CMD + S или Файл -> Сохранить / Сохранить как), он хочет сохранить родительский документ, а не текущий документ (как указано [[NSDocumentController sharedDocumentController] currentDocument] и как указано в заголовке окна). и изменить точку).

Из прочтения документации NSResponder кажется, что цепочка должна быть такой:

Текущее представление -> Superview ( повтор ) -> Window -> WindowController -> Document -> DocumentController -> Application.

Я не уверен, как архитектура на основе документов настраивает цепочку респондента (то есть, как она помещает NSDocument и NSDocumentController в цепочку), поэтому я хотел бы отладить ее, но я не уверен, где смотреть. Как я могу получить доступ к цепочке респондента в любой момент времени?

Ответы [ 5 ]

42 голосов
/ 22 ноября 2010

Вы можете выполнить итерацию по цепочке респондента, используя nextResponder метод NSResponder.Для вашего примера вы должны иметь возможность начать с текущего представления, а затем многократно распечатывать результат вызова его в цикле следующим образом:

NSResponder *responder = currentView;
while ((responder = [responder nextResponder])) {
     NSLog(@"%@", responder);
}
10 голосов
/ 01 марта 2015

Вот еще одна версия для пользователей Swift:

func printResponderChain(_ responder: UIResponder?) {
    guard let responder = responder else { return; }

    print(responder)
    printResponderChain(responder.next)
}

Просто позвоните себе, чтобы распечатать цепочку респондента, начиная с себя.

printResponderChain(self)
6 голосов
/ 10 октября 2017

Я немного улучшу ответ категории «Ответчик», используя метод класса, который кажется более «полезным» при отладке (вам не нужно разбивать конкретное представление или что-то в этом роде).

Код предназначен для Какао, но должен быть легко переносим для UIKit.

@interface NSResponder (Inspect)

+ (void)inspectResponderChain;

@end

@implementation NSResponder (Inspect)

+ (void)inspectResponderChain
{
  NSWindow *mainWindow = [NSApplication sharedApplication].mainWindow;

  NSLog(@"Responder chain:");
  NSResponder *responder = mainWindow.firstResponder;
  do
  {
    NSLog(@"\t%@", [responder debugDescription]);
  }
  while ((responder = [responder nextResponder]));
}

@end
3 голосов
/ 11 октября 2015

Вы также можете добавить категорию к классу UIResponder с помощью соответствующего метода, который может использоваться любым подклассом UIResponder.

@interface UIResponder (Inspect)

- (void)inspectResponderChain; // show responder chain including self

@end

@implementation UIResponder (Inspect)

- (void)inspectResponderChain  
{
    UIResponder *x = self;
    do {
        NSLog(@"%@", x);
    }while ((x = [x nextResponder]));
}
@end

Чем вы можете использовать этот метод где-то в коде, как в примере ниже:

- (void)viewDidLoad {
    ...
    UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    [self.view addSubview:myView];
    [myView inspectResponderChain]; // UIView is a subclass of UIResponder
    ...
}
1 голос
/ 09 февраля 2017

Swift:

extension UIResponder {
    var responderChain: [UIResponder] {
        var chain = [UIResponder]()
        var nextResponder = next
        while nextResponder != nil {
            chain.append(nextResponder!)
            nextResponder = nextResponder?.next
        }
        return chain
    }
}

// ...

print(self.responderChain)
...