-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathXSViewController.m
182 lines (156 loc) · 7.74 KB
/
XSViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//
// XSViewController.m
// View Controllers
//
// Created by Jonathan Dann and Cathy Shive on 14/04/2008.
//
// Copyright (c) 2008 Jonathan Dann and Cathy Shive
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// If you use it, acknowledgement in an About Page or other appropriate place would be nice.
// For example, "Contains "View Conrtollers" by Jonathan Dann and Cathy Shive" will do.
#import "XSViewController.h"
#import "XSWindowController.h"
#pragma mark Private API
@interface XSViewController (Private)
// This method is made private so users of the class can't (easily) set the children array whenever they want, they must use the indexed accessors provided. If this was public then our methods that mantain the responder chain and pass the represented object and window controller to the children would be subverted. Alternatively the setter could set all the required variables on the objects in the newChildren array, but the API then becomes a little clunkier.
- (void)setChildren:(NSMutableArray *)newChildren;
@end
@implementation XSViewController (Private)
- (void)setChildren:(NSMutableArray *)newChildren;
{
if (_children == newChildren)
return;
NSMutableArray *newChildrenCopy = [newChildren mutableCopy];
[_children release];
_children = newChildrenCopy;
}
@end
#pragma mark -
#pragma mark Public API
@implementation XSViewController
#pragma mark Accessors
@synthesize parent = _parent;
@synthesize windowController = _windowController;
@synthesize children = _children;
#pragma mark Designated Initialiser
- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle windowController:(XSWindowController *)windowController;
{
if (![super initWithNibName:name bundle:bundle])
return nil;
self.windowController = windowController; // non-retained to avoid retain cycles
self.children = [NSMutableArray array]; // set up a blank mutable array
return self;
}
// ---------------------------------
// This is the NSViewController's designated initialiser, which we override to call our own. Any subclasses that call this will then set up our intance variables properly.
// ---------------------------------
- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle;
{
// [NSException raise:@"XSViewControllerException" format:[NSString stringWithFormat:@"An instance of an XSViewController concrete subclass was initialized using the NSViewController method -initWithNibName:bundle: all view controllers in the enusing tree will have no reference to an XSWindowController object and cannot be automatically added to the responder chain"]];
@throw [NSException exceptionWithName:@"XSViewControllerException"
reason:@"An instance of an XSViewController concrete subclass was initialized using the NSViewController method -initWithNibName:bundle: all view controllers in the enusing tree will have no reference to an XSWindowController object and cannot be automatically added to the responder chain"
userInfo:nil];
return nil;
}
- (void)dealloc;
{
// removed the self.parent = nil and self.windowController = nil as this can mask bugs that may occurr if the user sends a message to either of those
[self.children release];
[super dealloc];
}
#pragma mark Indexed Accessors
- (NSUInteger)countOfChildren;
{
return [self.children count];
}
- (XSViewController *)objectInChildrenAtIndex:(NSUInteger)index;
{
return [self.children objectAtIndex:index];
}
// ------------------------------------------
// This will add a new XSViewController subclass to the end of the children array.
// ------------------------------------------
- (void)addChild:(XSViewController *)viewController;
{
[self insertObject:viewController inChildrenAtIndex:[self.children count]];
}
- (void)removeChild:(XSViewController *)viewController;
{
[self.children removeObject:viewController];
}
- (void)removeObjectFromChildrenAtIndex:(NSUInteger)index;
{
[self.children removeObjectAtIndex:index];
[(XSWindowController *)self.windowController patchResponderChain]; // each time a controller is removed then the repsonder chain needs fixing
}
- (void)insertObject:(XSViewController *)viewController inChildrenAtIndex:(NSUInteger)index;
{
[self.children insertObject:viewController atIndex:index];
[viewController setParent:self];
[self.windowController patchResponderChain];
}
- (void)insertObjects:(NSArray *)viewControllers inChildrenAtIndexes:(NSIndexSet *)indexes;
{
[self.children insertObjects:viewControllers atIndexes:indexes];
[viewControllers makeObjectsPerformSelector:@selector(setParent:) withObject:self];
[self.windowController patchResponderChain];
}
- (void)insertObjects:(NSArray *)viewControllers inChildrenAtIndex:(NSUInteger)index;
{
[self insertObjects:viewControllers inChildrenAtIndexes:[NSIndexSet indexSetWithIndex:index]];
}
# pragma mark Utilities
// ------------------------------------------
// This method is not used in the example but does demonstrates an important point of our setup: the root controller in the tree should have parent = nil. If you'd rather set the parent of the root node to the window controller, this method must be modified to check the class of the parent object.
// ------------------------------------------
- (XSViewController *)rootController;
{
XSViewController *root = self.parent;
if (!root) // we are the top of the tree
return self;
while (root.parent) // if this is nil then there is no parent, the whole system is based on the idea that the top of the tree has nil parent, not the windowController as its parent.
root = root.parent;
return root;
}
// ---------------------------------------
// A top-down tree sorting method. Recursively calls itself to build up an array of all the nodes in the tree. If one thinks of a file and folder setup, then this would add all the contents of a folder to the array (ad infinitum) to the array before moving on to the next folder at the same level
// ----------------------------------------
- (NSArray *)descendants;
{
NSMutableArray *array = [NSMutableArray array];
for (XSViewController *child in self.children) {
[array addObject:child];
if ([child countOfChildren] > 0)
[array addObjectsFromArray:[child descendants]];
}
return [[array copy] autorelease]; // return an immutable array
}
// --------------------------------------
// Any manual KVO or bindings that you have set up (other than to the representedObject) should be removed in this method. It is called by the window controller on in the -windowWillClose: method. After this the window controller can safely call -dealloc without any warnings that it is being deallocated while observers are still registered.
// --------------------------------------
- (void)removeObservations
{
[self.children makeObjectsPerformSelector:@selector(removeObservations)];
}
@end