diff --git a/AppKit/CPView.j b/AppKit/CPView.j
index a142e82a9..dd7304a50 100644
--- a/AppKit/CPView.j
+++ b/AppKit/CPView.j
@@ -112,6 +112,11 @@ CPViewHeightSizable = 16;
*/
CPViewMaxYMargin = 32;
+_CPViewWillAppearNotification = @"CPViewWillAppearNotification";
+_CPViewDidAppearNotification = @"CPViewDidAppearNotification";
+_CPViewWillDisappearNotification = @"CPViewWillDisappearNotification";
+_CPViewDidDisappearNotification = @"CPViewDidDisappearNotification";
+
CPViewBoundsDidChangeNotification = @"CPViewBoundsDidChangeNotification";
CPViewFrameDidChangeNotification = @"CPViewFrameDidChangeNotification";
@@ -404,7 +409,6 @@ var CPViewHighDPIDrawingEnabled = YES;
return self;
}
-
/*!
Sets the tooltip for the receiver.
@@ -599,6 +603,7 @@ var CPViewHighDPIDrawingEnabled = YES;
// Remove the view from its previous superview.
[aSubview _removeFromSuperview];
+ [aSubview _postViewWillAppearNotification];
// Set ourselves as the superview.
[aSubview _setSuperview:self];
}
@@ -685,6 +690,7 @@ var CPViewHighDPIDrawingEnabled = YES;
[[self window] _dirtyKeyViewLoop];
[_superview willRemoveSubview:self];
+ [self _postViewWillDisappearNotification];
[_superview._subviews removeObjectIdenticalTo:self];
@@ -1616,18 +1622,40 @@ var CPViewHighDPIDrawingEnabled = YES;
while (view = [view superview]);
}
+ [self _postViewWillDisappearNotification];
[self _recursiveGainedHiddenAncestor];
}
else
{
[self setNeedsDisplay:YES];
+ [self _postViewWillAppearNotification];
[self _recursiveLostHiddenAncestor];
}
_isHidden = aFlag;
}
+- (void)_postViewWillAppearNotification
+{
+ [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewWillAppearNotification object:self userInfo:nil];
+}
+
+- (void)_postViewDidAppearNotification
+{
+ [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewDidAppearNotification object:self userInfo:nil];
+}
+
+- (void)_postViewWillDisappearNotification
+{
+ [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewWillDisappearNotification object:self userInfo:nil];
+}
+
+- (void)_postViewDidDisappearNotification
+{
+ [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewDidDisappearNotification object:self userInfo:nil];
+}
+
- (void)_setSuperview:(CPView)aSuperview
{
var hasOldSuperview = (_superview !== nil),
@@ -1636,12 +1664,18 @@ var CPViewHighDPIDrawingEnabled = YES;
newSuperviewIsHidden = hasNewSuperview && [aSuperview isHiddenOrHasHiddenAncestor];
if (!newSuperviewIsHidden && oldSuperviewIsHidden)
- [self _recursiveLostHiddenAncestor];
+ [self _recursiveLostHiddenAncestor];
if (newSuperviewIsHidden && !oldSuperviewIsHidden)
[self _recursiveGainedHiddenAncestor];
_superview = aSuperview;
+
+ if (hasOldSuperview)
+ [self _postViewDidDisappearNotification];
+
+ if (hasNewSuperview)
+ [self _postViewDidAppearNotification];
}
- (void)_recursiveLostHiddenAncestor
@@ -3669,7 +3703,6 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
_window = [aCoder decodeObjectForKey:CPViewWindowKey];
_superview = [aCoder decodeObjectForKey:CPViewSuperviewKey];
-
// We have to manually add the subviews so that they will receive
// viewWillMoveToSuperview: and viewDidMoveToSuperview:
_subviews = [];
diff --git a/AppKit/CPViewController.j b/AppKit/CPViewController.j
index 254fdfb4c..2c8d4a0de 100644
--- a/AppKit/CPViewController.j
+++ b/AppKit/CPViewController.j
@@ -213,7 +213,7 @@ var CPViewControllerCachedCibs;
if (anError == nil)
{
var data = [CPData dataWithRawString:aData],
- aCib = [[CPCib alloc] _initWithData:data bundle:_cibBundle cibName:_cibName];
+ aCib = [[CPCib alloc] _initWithData:data bundle:_cibBundle cibName:_cibName];
[CPViewControllerCachedCibs setObject:aCib forKey:_cibName];
[aCib instantiateCibWithExternalNameTable:_cibExternalNameTable];
@@ -286,13 +286,16 @@ var CPViewControllerCachedCibs;
- (void)_viewDidLoad
{
- [self _viewDidLoadWithCompletionHandler:function() {
- [self viewDidLoad];
- }];
+ [self willChangeValueForKey:"isViewLoaded"];
+ [self viewDidLoad];
+ _isViewLoaded = YES;
+ [self didChangeValueForKey:"isViewLoaded"];
}
- (void)_viewDidLoadWithCompletionHandler:(Function)aHandler
{
+ [self _registerOrUnregister:YES notificationsForView:_view];
+
[self willChangeValueForKey:"isViewLoaded"];
aHandler(_view, nil);
_isViewLoaded = YES;
@@ -325,6 +328,9 @@ var CPViewControllerCachedCibs;
{
var willChangeIsViewLoaded = (_isViewLoaded == NO && aView != nil) || (_isViewLoaded == YES && aView == nil);
+ [self _registerOrUnregister:NO notificationsForView:_view];
+ [self _registerOrUnregister:YES notificationsForView:aView];
+
if (willChangeIsViewLoaded)
[self willChangeValueForKey:"isViewLoaded"];
@@ -340,6 +346,30 @@ var CPViewControllerCachedCibs;
return NO;
}
+- (void)_registerOrUnregister:(BOOL)shouldRegister notificationsForView:(CPView)aView
+{
+ if (aView === nil)
+ return;
+
+ var center = [CPNotificationCenter defaultCenter],
+ notifs_to_sel = @{_CPViewWillAppearNotification : @"viewWillAppear",
+ _CPViewDidAppearNotification : @"viewDidAppear",
+ _CPViewWillDisappearNotification : @"viewWillDisappear",
+ _CPViewDidDisappearNotification : @"viewDidDisappear"};
+
+ [notifs_to_sel enumerateKeysAndObjectsUsingBlock:function(notif, selString, stop)
+ {
+ var selector = CPSelectorFromString(selString);
+ if ([self implementsSelector:selector])
+ {
+ if (shouldRegister)
+ [center addObserver:self selector:selector name:notif object:aView];
+ else
+ [center removeObserver:self name:notif object:aView];
+ }
+ }];
+}
+
@end
diff --git a/Tests/AppKit/BundleTest/Info.plist b/Tests/AppKit/BundleTest/Info.plist
new file mode 100644
index 000000000..bb4429725
--- /dev/null
+++ b/Tests/AppKit/BundleTest/Info.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ Main cib file base name
+ MainMenu.cib
+ CPBundleName
+ BundleTest
+
+
diff --git a/Tests/AppKit/BundleTest/Resources/NSViewController.cib b/Tests/AppKit/BundleTest/Resources/NSViewController.cib
new file mode 100644
index 000000000..45f5c8599
--- /dev/null
+++ b/Tests/AppKit/BundleTest/Resources/NSViewController.cib
@@ -0,0 +1 @@
+280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;0E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;4E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;0E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;1;7E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;1;9E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;11E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;13E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;15E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;18E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;19E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;12E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;2;20E;E;D;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;11E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;18E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;21E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;22E;E;D;K;10;$classnameS;18;_CPCibClassSwapperK;8;$classesA;S;18;_CPCibClassSwapperS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;17E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;23E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;24E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;24E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;25E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;26E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;27E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;27E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;28E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;29E;K;30;_CPCibClassSwapperClassNameKeyD;K;6;CP$UIDd;2;30E;K;38;_CPCibClassSwapperOriginalClassNameKeyD;K;6;CP$UIDd;2;31E;E;S;16;CPViewControllerD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;4;viewS;13;CPApplicationd;1;0S;20;{{0, 0}, {480, 272}}d;2;12S;6;normalS;6;{1, 1}F;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;6;NSViewS;6;CPViewE;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E;
\ No newline at end of file
diff --git a/Tests/AppKit/BundleTest/Resources/NSViewController.xib b/Tests/AppKit/BundleTest/Resources/NSViewController.xib
new file mode 100644
index 000000000..9a80f715c
--- /dev/null
+++ b/Tests/AppKit/BundleTest/Resources/NSViewController.xib
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/AppKit/CPViewControllerTest.j b/Tests/AppKit/CPViewControllerTest.j
new file mode 100644
index 000000000..7ebed52fc
--- /dev/null
+++ b/Tests/AppKit/CPViewControllerTest.j
@@ -0,0 +1,89 @@
+@import
+@import
+
+var methodsCalled;
+
+@implementation CPViewControllerTest : OJTestCase
+{
+ CPBundle bundle;
+}
+
+- (void)setUp
+{
+ bundle = [CPBundle bundleWithPath:@"Tests/AppKit/BundleTest"];
+ [bundle loadWithDelegate:self];
+}
+
+- (void)bundleDidFinishLoading:(CPBundle)aBundle
+{
+
+}
+
+- (void)testViewControllerCallbacks
+{
+ methodsCalled = @[];
+
+ var expectedResult = @[@"viewDidLoad",
+ @"viewWillAppear",
+ @"viewDidAppear",
+ @"viewWillDisappear",
+ @"viewDidDisappear"];
+
+ [self assertTrue:[bundle isLoaded]];
+ var viewController = [[ViewController alloc] initWithCibName:@"NSViewController.cib" bundle:bundle];
+ [self assertNotNull:viewController];
+
+ var superview = [[CPView alloc] initWithFrame:CGRectMakeZero()];
+ var view = [viewController view];
+
+ [superview addSubview:view];
+ [view removeFromSuperview];
+ [self assert:expectedResult equals:methodsCalled];
+
+ // Explicitely change the view.
+ methodsCalled = [];
+ expectedResult = @[@"viewWillAppear",
+ @"viewDidAppear",
+ @"viewWillDisappear",
+ @"viewDidDisappear"];
+
+ var newView = [[CPView alloc] initWithFrame:CGRectMake(0,0,100,100)];
+ [viewController setView:newView];
+
+ [superview addSubview:newView];
+ [newView removeFromSuperview];
+ // Checks that with receive notifs from the new view and not from the old view.
+ [self assert:expectedResult equals:methodsCalled];
+}
+
+@end
+
+@implementation ViewController : CPViewController
+{
+}
+
+- (void)viewDidLoad
+{
+ [methodsCalled addObject:_cmd];
+}
+
+- (void)viewDidAppear
+{
+ [methodsCalled addObject:_cmd];
+}
+
+- (void)viewWillAppear
+{
+ [methodsCalled addObject:_cmd];
+}
+
+- (void)viewDidDisappear
+{
+ [methodsCalled addObject:_cmd];
+}
+
+- (void)viewWillDisappear
+{
+ [methodsCalled addObject:_cmd];
+}
+@end
diff --git a/Tests/AppKit/CPViewTest.j b/Tests/AppKit/CPViewTest.j
index 78812d2fc..93edeb549 100644
--- a/Tests/AppKit/CPViewTest.j
+++ b/Tests/AppKit/CPViewTest.j
@@ -905,6 +905,21 @@ var updateTrackingAreasCalls,
[self assert:expectedResult equals:methodCalled];
}
+- (void)testAddViewRemoveView
+{
+ var expectedResult = [@"viewWillMoveToSuperview_view2",
+ @"viewDidMoveToSuperview_view2",
+ @"viewWillMoveToSuperview_view2",
+ @"viewDidMoveToSuperview_view2",
+ @"viewWillMoveToWindow_view2",
+ @"viewDidMoveToWindow_view2"];
+
+ [view1 addSubview:view2];
+ [view2 removeFromSuperview];
+
+ [self assert:expectedResult equals:methodCalled];
+}
+
- (void)testViewGainedHiddenAncestor
{
var expectedResult = [@"viewDidHide_view1",
@@ -917,14 +932,16 @@ var updateTrackingAreasCalls,
[view1 setHidden:YES];
[view2 addSubview:view3];
+ CPLog.warn("will add view2");
[view1 addSubview:view2];
- [self assert:expectedResult equals:methodCalled];
- [self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES"];
+ [self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected " + [view2 identifier] + "isHiddenOrHasHiddenAncestor = YES"];
[self assertTrue: [view3 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES"];
[self assertFalse:[view2 isHidden]];
[self assertFalse:[view3 isHidden]];
+
+ [self assert:expectedResult equals:methodCalled];
}
- (void)testRemoveViewsHiddenByAncestor