Slop is Good
3 months ago
NSArray* arr1 = [[NSArray alloc] init];
And you have some data in it. Most people realize that the following assignment
NSArray* arr2 = arr1;
doesn't make a copy of anything except the pointer. What you end up with is two pointers pointing to the same array. To make a shallow copy of arr1, you can do something like this:
NSArray* arr2 = [[NSArray alloc] initWithArray:arr1];
This is a "shallow" copy because although it creates a new array, all its contents are pointers which point to the same contents of arr1. The Objective-C frameworks provide a copy method which you would think would work, but it too creates a shallow copy. The following is just another way of saying the above:
NSArray* arr2 = [arr1 copy];
This will also allocate a new NSArray object (and retain it) and put in the same pointers from arr1 into arr2.
To get a deep copy of arr1, meaning not only a new array but a new copy of each element in the array, you need something different:
NSArray* arr2 = [[NSArray alloc] initWithArray:arr1 copyItems:YES];
NSDictionary objects also provide this form of initializer to allow deep copying of a dictionary container.
All of this is fairly muddy, but when we start talking about mutable containers, it gets worse. Consider the following code:
NSMutableDictionary* firstDict = [[NSMutableDictionary alloc] initWithCapacity:10];
// add some data to the dictionary
[firstDict setObject:@"hi" forKey:@"one"];
// now make a copy of it
NSMutableDictionary* secondDict = [firstDict copy];
// add some more data to the second dictionary
[secondDict setObject:@"there" forKey:@"two"];
// BOOM!
The "BOOM!" above means an exception is thrown. Why? Because the dictionary secondDict, although declared as an NSMutableDictionary and copied from another NSMutableDictionary is actually an NSDictionary.
It is reasonable to think that copying a mutable into another mutable would create a mutable copy, but it is not so in Objective-C. The problem is that the copy method creates an immutable object.
The solution here is to use the mutableCopy method, which is a special copy method just for mutable containers. So the line above should be replaced with:
NSMutableDictionary* secondDict = [firstDict mutableCopy];
So what if you want a deep copy of a mutable container? In this case, you can use the special NSDictionary initializer that takes the copyItems parameter:
NSMutableDictionary* secondDict = [[NSMutableDictionary alloc] initWithDictionary:firstDict copyItems:YES];
(Note that to use copy, mutableCopy, or the initializers that take copyItems, the objects that are contained in the array or dictionary must conform to the NSCopying and/or NSMutableCopying protocols.)
What these examples show is that the initializers behave as expected, but the copy method is only for immutable containers and the mutableCopy method is for mutable containers. Because of this confusion, I prefer to use the initializer for both my shallow copying and deep copying; simply change the copyItems to NO for a shallow copy.
But that was my situation. If your controller is at the end of the UINavigationBar hiearchy, meaning you cannot have another controller pushed onto yours, then you are safe assuming that the viewWillDisappear is being called for the scenario where your controller is being popped off. Take caution, though. Should you later change the behavior of your code to allow additional controllers to be pushed, your viewWillDisappear will get invoked in what may be an inappropriate time. One other thing I should mention. There is this warning in the Apple reference in each of the four methods listed above:// --------------------------------------------------------- //
// YourController.h @interface YourController : UIViewController { BOOL ignoreDisappear; } @end // --------------------------------------------------------- // // YourController.m // this helper method will push a new controller onto the navigation stack - (void) myPushController:(UIViewController*) newController { // first, set our flag to ignore the next viewWillDisappear message ignoreDisappear = YES; // now do the push [self.navigationController pushViewController:newController animate:YES]; [newController release]; } - (void) viewWillDisappear:(BOOL) animate { // do we care about this event? if(ignoreDisappear == NO) { // this is your back button handle logic //... } [super viewWillDisappear:animate]; } - (void) viewWillAppear:(BOOL) animate { // clear the flag ignoreDisappear = NO; [super viewWillAppear:animate]; }
Warning: If the view belonging to a view controller is added to a view hierarchy directly, the view controller will not receive this message. If you insert or add a view to the view hierarchy, and it has a view controller, you should send the associated view controller this message directly. Failing to send the view controller this message will prevent any associated animation from being displayed.I'm not real clear on what the phrase "added to a view hierarchy directly" means, but there are numerous reported problems related to this. I don't have these problems myself, but they are reported here and here.
parser:didStartElement
and parser:foundCharacters
methods tend to be very messy with lots of if/else if statements, and you have to keep track of your own context via instance variables.
@interface XMLTreeNode : NSObject {
XMLTreeNode* parent;
NSString* name; // element name
NSMutableDictionary* attributes;
NSMutableString* text; // from foundCharacters()
NSMutableDictionary* children; // key is child's element name, value is NSMutableArray of tree nodes
}
Most of these properties are obvious. The text property is a concatenation of all the free-floating text that were inside an element. For example, the text "Hello World" would be stored in the text property:
<elementName>Hello World</elementName>
The choice of using a dictionary for the children property might be a curious one. The key of the dictionary is an NSString, which is the element name. The value of the dictionary is an NSArray of XMLTreeNode objects. To illustrate, here is some sample XML:
<stuff>
<item attr="value1"/>
<item attr="value2"/>
<note>TEXT</note>
</stuff>
Parsing this would create a root node with one child whose name is "stuff". The "stuff" node has two entries in its children dictionary: one entry for "item" and one for "note". The value for these entries is an NSArray of XMLTreeNode objects. The "item" array will have two nodes, and the "note" array will have only one.
The use of a dictionary allows us to quickly search for children, as we'll see later. The downside of this is that the order of the "item" and "note" child nodes for "stuff" will not be retained. This is a side-effect of dictionaries not preserving the order. However, proper XML should not care about the order of the elements, only that the hierarchy is correct.
I should note that the order of children of the same element is preserved. For example, when you query the tree, you have no idea if the "item" children will come before the "next" child or not. What you do know, though, is that you will get the item with "value1" before the item with "value2". This is important for the indexing scheme described below.
XMLTreeParser* parser = [[XMLTreeParser alloc] init];
This is a very simple implementation, so as it currently stands, an instance can only parse one XML input. There is no way to reuse a parser instance to parse some other XML. But that would be an easy exercise for the reader to address.
Now to start parsing, call the parse method and provide the XML data:
XMLTreeNode* root = [parser parse:xmlData];
Again, this simple implementation only handles XML passed directly to it, not indirectly through a filename. When this returns, your XML has been completely parsed. The beautiful part is you don't have to write all those event handlers. What you get back is the root node of the tree. This node does not have any useful data other than its children. This return will be nil if there is any problem parsing the supplied XML.
NSArray* stuffs = [root findChildren:@"stuff"];
XMLTreeNode* stuff = [stuffs objectAtIndex:0];
NSArray* items = [stuff findChildren:@"item"];
OK, this is great, but a little cumbersome. If you know that there is only one "stuff" element, it's a shame you have to get back an array of them. So there is the findChild method, which returns a single XMLTreeNode object:
XMLTreeNode stuff = [root findChild:@"stuff"];
What if there are more than one "stuff" elements? This version of findChild: always takes the first element in the array. If you want a different element, you must use findChild:at:
XMLTreeNode* item2 = [stuff findChild:@"item" at:1];
This will return the second "item" node.
XMLTreeNode* item1 = [root findChild:@"stuff/item"];
This basically combines two searches into 1. Now, for your 10-deep XML, you could use something like this:
XMLTreeNode* deep = [root findChild:@"stuff/items/item/something/lists/list/test"];
That's 7 searches in 1.
// I want the 3rd doodad in the 4th thingamajig of the 2nd whatchacallit
XMLTreeNode* doodad3 = [root findChild:@"whatchacallit[1]/thingamajig[3]/doodad[2]"];
Remember that these indeces are 0-based, so they are one less than what you'd expect.
Now you can dig around in your XML to your heart's content. Be aware that if the query fails anywhere along the path, it will return nil. So if you get nil back from a find method, you can assume that it didn't find what you were looking for. XMLTreeNode* searchRoot = [root findChild:@"stuff/same/old/path"];
XMLTreeNode* newThing = [searchRoot findChild:@"cool/new/thing"];
Think of this as changing your current directory so you don't always have to type in the full path.
NSString* value = [root findChildAttr:@"stuff/item[1].value"];
This would get the attribute "value" from the second "item" element under "stuff".
But what it does now is all that was required for my current project. Other enhancements could follow as the needs arise.
[self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"New Title" style:UIBarButtonItemStyleBordered target:nil action:nil];
- (void) viewDidLoad
{
// change the back button and add an event handler
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:@"Pages"
style:UIBarButtonItemStyleBordered
target:self
action:@selector(handleBack:)];
}
- (void) handleBack:(id)sender
{
// do your custom handler code here
// make sure you do this!
// pop the controller
[self.navigationController popViewControllerAnimated:YES];
}
If you still want this button to behave like a back button, it is important to put the popViewControllerAnimated: call in your handler.
I should also say that this leftBarButtonItem should be set on the controller who will actually show the back button, unlike the backBarButtonItem.
Now the reason I stated this wasn't a perfect solution is the following: The "special" back button looks different. Here is what the standard backBarButtonItem looks like:
TableFormEditor* add = [[TableFormEditor alloc] initForAdd:self];
All you provide is the delegate, typically self. The delegate must conform to the protocol TableFormEditorDelegate: @protocol TableFormEditorDelegate
@optional
- (void) formDidCancel;
- (void) formDidSave:(NSMutableDictionary*) newData;
- (void) formDidDelete;
@end
All these methods are optional, but obviously without the formDidSave: you don't have very functional software. To initialize for edit more, use the initForEdit: method. TableFormEditor* edit = [[TableFormEditor alloc] initForEdit:self];
Just as with the add mode, you pass in a delegate to handle the same protocol. After this, it should start normally. I didn't experience this when I installed version 0.46, so this may be fixed already. When you fire up Inkscape, you see a blank canvas that looks like this:
$ mkdir ~/.fontconfig