Merge pull request #3123 from daboe01/new-viewcontroller-responder-chain-issue

fixed: CPViewController doesn't participate fully in responder chain
This commit is contained in:
daboe01
2025-07-05 16:41:23 +02:00
committed by GitHub
2 changed files with 60 additions and 1 deletions
+26 -1
View File
@@ -166,7 +166,7 @@ var CPViewControllerCachedCibs;
if (!cib)
{
// if the cib isn't cached yet : fetch it and cache it
// if the cib isn't cached yet: fetch it and cache it
cib = [[CPCib alloc] initWithCibNamed:_cibName bundle:_cibBundle];
[CPViewControllerCachedCibs setObject:cib forKey:_cibName];
}
@@ -261,6 +261,11 @@ var CPViewControllerCachedCibs;
if (_view == nil && [cibOwner isKindOfClass:[CPDocument class]])
[self setView:[cibOwner valueForKey:@"view"]];
// If the view was just loaded, we must set its next responder.
// This is the first half of inserting the controller into the responder chain.
if (_view)
[_view setNextResponder:self];
if (!_view)
{
var reason = [CPString stringWithFormat:@"View for %@ could not be loaded from Cib or no view specified. Override loadView to load the view manually.", self];
@@ -408,12 +413,28 @@ var CPViewControllerCachedCibs;
[self willChangeValueForKey:"isViewLoaded"];
_view = aView;
// When the view is set manually, we must set its next responder.
if (_view)
[_view setNextResponder:self];
_isViewLoaded = aView != nil;
if (willChangeIsViewLoaded)
[self didChangeValueForKey:"isViewLoaded"];
}
/*!
@method nextResponder
@discussion The CPViewController implementation of this method returns the superview
of the view controller's view. This is the second half of the insertion,
completing the chain: view -> viewController -> superview.
*/
- (id)nextResponder
{
return [_view superview];
}
- (BOOL)automaticallyNotifiesObserversOfIsViewLoaded
{
return NO;
@@ -465,6 +486,10 @@ var CPViewControllerViewKey = @"CPViewControllerViewKey",
if (self)
{
_view = [aCoder decodeObjectForKey:CPViewControllerViewKey];
// When the view is unarchived, we must also set its next responder.
if (_view)
[_view setNextResponder:self];
_title = [aCoder decodeObjectForKey:CPViewControllerTitleKey];
_cibName = [aCoder decodeObjectForKey:CPViewControllerCibNameKey];
+34
View File
@@ -2,6 +2,7 @@
@import <AppKit/CPApplication.j>
var methodsCalled;
var testResponderChainActionCalled;
@implementation CPViewControllerTest : OJTestCase
{
@@ -56,6 +57,28 @@ var methodsCalled;
[self assert:expectedResult equals:methodsCalled];
}
- (void)testResponderChain
{
testResponderChainActionCalled = NO;
// 1. Create the controller, its view, and a superview.
var viewController = [[ResponderTestViewController alloc] initWithCibName:nil bundle:nil externalNameTable:@{}];
var view = [viewController view];
// 2. Assert the responder chain is correctly wired.
// The view's next responder should be its controller.
[self assert:viewController equals:[view nextResponder] message:@"The view controller should be the next responder of its view."];
// 3. Test that an action sent to the view is handled by the controller.
var wasHandled = [view tryToPerform:@selector(testAction:) with:nil];
[self assertTrue:wasHandled message:@"The action should be handled by the responder chain."];
[self assertTrue:testResponderChainActionCalled message:@"The view controller's action method should have been called."];
// 4. Test that the chain unwires correctly when the view is removed.
[view removeFromSuperview];
[self assert:nil equals:[viewController nextResponder] message:@"The next responder should be nil after the view is removed from its superview."];
}
@end
@implementation ViewController : CPViewController
@@ -87,3 +110,14 @@ var methodsCalled;
[methodsCalled addObject:_cmd];
}
@end
@implementation ResponderTestViewController : CPViewController
{
}
- (void)testAction:(id)sender
{
testResponderChainActionCalled = YES;
}
@end