I'm going through a book on OS X programing as a refresher and have a document app set up with an array controller, tableView etc. The chapter calls for implementing undo support by hand using NSInvocation. In the chapter, they call for adding a create employee method and manually, adding outlets to the NSArrayController, and connecting my add button to the new method instead of the array controller.
Instead I did this with my method for inserting new objects:
-(void)insertObject:(Person *)object inEmployeesAtIndex:(NSUInteger)index {
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]removeObjectFromEmployeesAtIndex:index];
if (![undoManager isUndoing]) {
[undoManager setActionName:#"Add Person"];
}
[self startObservingPerson:object];
[[self employees]insertObject:object atIndex:index];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Wait then start editing
[[self tableView]editColumn:0 row:index withEvent:nil select:YES];
});
}
This works ok (looks a bit silly), but I was wondering the what issues could arise from this. I've done this elsewhere in order to execute code after an animation finished (couldn't figure out a better way).
Thanks in advance.
Why are you delaying the invocation of -editColumn:row:withEvent:select:?
Anyway, the risks are that something else will be done between the end of this -insertObject:... method and when the dispatched task executes. Perhaps something that will change the contents of the table view such that index no longer refers to the just-added employee.
Related
Maybe I'm doing something wrong but if I call removeRowsAtIndexes: on a WKInterfaceTable instance like so:
NSIndexSet *indexes = [[NSIndexSet alloc] initWithIndex:index];
[self.table removeRowsAtIndexes:indexes];
Should the table not update? Logging the numberOfRows reported by the table shows the number correctly reduced by 1, but on screen nothing changes.
Trying to select a row after this point can actually result in selecting the row below, so clearly the table has recorded the deletion but at least in the simulator it isn't showing.
Has anyone else expereinced this? Is this a bug or do I need to force the table to refresh somehow?
Your class must be fully loaded before you calling removeRows AtIndexes. (when watch interface is visible to user).
- (void)willActivate {
[super willActivate];
NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:index];
[self.table removeRowsAtIndexes:indexes];
}
At what point did you call the method removeRowsAtIndexes in your code ?
I've implemented an undo function in my paint application. The idea is that the undo sets the layer to draw from an array of saved layers. This method is called right before the view is added to for drawing. Note that layerArray is a C array.
-(void)updateLayerUndo
{
CGLayerRef undoLayer = CGLayerCreateWithContext([self.theCanvas viewContext], self.theCanvas.bounds.size, nil);
layerArray[undoCount] = undoLayer;
undoCount += 1;
[[self.undoer prepareWithInvocationTarget:self]performUndoOrRedoWithValueForLayer:layerArray[undoCount - 1]];
}
When the user undoes, it calls the registered method, naturally:
-(void)performUndoOrRedoWithValueForLayer:(CGLayerRef)aLayer
{
undoCount -= 1;
self.theCanvas.myLayer = aLayer;
[[NSNotificationCenter defaultCenter]postNotificationName:#"UndoOrRedoPerformed" object:nil];
[self.theCanvas setNeedsDisplay:YES];
}
The view then does its drawing method, which really just draws the myLayer in the view's context. However, when the user undoes, the view clears out completely instead of incrementally. I feel like I'm missing something, or maybe I don't understand NSUndoManager. Oh, and the methods I showed above are both in an NSDocument subclass, and theCanvas is an instance of an NSView subclass.
I figured it out. The confusion I was having is I was unclear how to set redo, so I misinterpreted what the documentation meant about the undo manager and redoes. I now know that to register a redo, I have to register a undo that is called while the manager is undoing.
iam using MagicalRecord (https://github.com/magicalpanda)
this wasnt working (not marging the MOCs)
- (void) foo {
NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext * localContext = [NSManagedObjectContext MR_contextForCurrentThread];
// parsing and core data operation on localContext here, savin objectIDs
[localContext MR_saveNestedContexts];
[[NSOperationQueue mainQueue] addOperationWithBlock:^(){
onParseFinished(parsedItemObjectIDs);
}];
}];
[self.operationQueue addOperation:operation];
}
had to replace it with this (ALSO APPLIED THE FIX https://github.com/magicalpanda/MagicalRecord/pull/221)
- (void) foo {
__block NSMutableArray * parsedItemsObjectIDs;
__block Class parsedItemsClass = [NSObject class];
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
// parsing and core data operation on localContext here, savin objectIDs
...
} completion:^(void){
onParseFinished(parsedItemsObjectIDs);
}];
}
Whats the issue with the first one? why it was not working and the changes were not merged?
The simple fact on not merging changes is my time is limited. I try to understand and validate all pull requests that come in, and being a one man shop means my time for open source (free, gratis, not-paying) work is more limited these days. However, I believe the fix was incorporated eventually, it just wasn't evident from the commit history.
As for why it didn't work in the first place, my hunch is that the contextForCurrentThread method returned something that wasn't useful. saveInBackground creates a new context every time it's called to give you a fresh work area. This is the main difference I believe.
I have a computational process that takes quite a bit of time to perform so a UIActivityIndicatorView seems appropriate. I have a button to initiate the computation.
I've tried putting the command [calcActivity startAnimating]; at the beginning of the computation in an IBAction and [calcActivity stopAnimating]; at the end of the computation but nothing shows.
Next, I created a new IBAction to contain the starting and stopping with a call to the computation IBAction and a dummy for loop just to give the startAnimating a little chance to get started between the two. This doesn't work either.
The skeletal code looks like this:
- (IBAction)computeNow:(id)sender {
[calcActivity startAnimating];
for (int i=0; i<1000; ++i) { }
[self calcStats];
[calcActivity stopAnimating];
}
- (IBAction)calcStats {
// do lots of calculations here
return;
}
Ok, as I commented, you should never performe complex calculations in your main thread. It not only leads to situations like yours, your app might also be rejected from the store.
Now, the reason for the UIActivityIndicatorView not being updated is, that the UI doesn't actually update itself e.g. when you call [calcActivity startAnimating]; Instead, it gets updated after your code ran through. In your case, that means that startAnimating and stopAnimating are getting called at once, so nothing really happens.
So, the 'easy' solution: Start a new thread, using either this techniques or, probably better, GCD.
Thanks for the nudge, Phlibbo. I'm new to this game and appreciate all the help. I didn't comprehend all the info on the links you provided, but it did prod me to search further for examples. I found one that works well. The IBAction 'computeNow' is triggered by the calculation button. The code now looks like this:
- (IBAction)computeNow {
[calcActivity startAnimating];
[self performSelector:#selector(calcStats) withObject:nil afterDelay:0];
return;
}
- (void) calcStats {
// Lots of tedious calculations
[calcActivity stopAnimating];
}
I have a question regarding using KVO-compliant methods to insert/remove objects from an array. I'm working through Aaron Hillegass' Cocoa Programming for Mac OS X and I saw the following line of code (in the insertObject:inEmployeesAtIndex: method:
[[undoManager prepareWithInvocationTarget:self] removeObjectFromEmployeesAtIndex:index];
Correct me if I'm wrong, but I always thought it was better to call mutableArrayValueForKey: and then removeObjectAtIndex:...so I tried changing the above line to this:
[[undoManager prepareWithInvocationTarget:[self mutableArrayValueForKey:#"employees"]] removeObjectAtIndex:index];
And it didn't work. Can someone explain the difference and why the first line works but the second line doesn't?
UPDATE: My removeObjectFromEmployeesAtIndex:index method is implemented to make my collection class (an instance of NSMutableArray) KVC-compliant. So ultimately, calling [[self mutableArrayValueForKey:#"employees"] removeObjectAtIndex:index]; should end up calling [self removeObjectFromEmployeesAtIndex:index];
In your update you say:
calling [[self mutableArrayValueForKey:#"employees"] removeObjectAtIndex:index]; should end up calling [self removeObjectFromEmployeesAtIndex:index];
Unfortunately this is not correct not matter what is in your removeObjectFromEmployeesAtIndex: method as NSMutableArray will never call any methods in your class. Since you seem to be trying to get undo/redo functionality you have to use a method like removeObjectFromEmployeesAtIndex:. Otherwise when you hit undo for adding an employee you will have no way to 'redo' adding that employee. You also could have issues with undo/redo for edits to individual employees. If you wanted to you could change the line in the removeObjectFromEmployeesAtIndex: method that reads [employees removeObjectAtIndex:index]; to [[self valueForKey:#"employees"] removeObjectAtIndex:index]; or [self.employees removeObjectAtIndex:index]; but there is really no reason to go this route.
Yes. The first line (from the book) is basically equivalent to this:
id tmp = [undoManager prepareWithInvocationTarget:self];
[tmp removeObejctFromEmployeesAtIndex:index];
Your code, however, is basically equivalent to this:
id tmp1 = [self mutableArrayValueForKey:#"employees"];
id tmp2 = [undoManager prepareWithInvocationTarget:tmp1];
[tmp2 removeObjectAtIndex:index];
In other words, the target that you're preparing the invocation with is different in your code (unless self happens to be the same object as [self mutableArrayValueForKey:#"employees"], which is doubtful).