Detect when SKNode is added as child - objective-c

I have a Polygon class that is used as data container, but for debugging purposes I want to draw polygons by adding them as children to an SKNode. When I do this I want the Polygon object to add border sprite children to itself. Because of performance reasons I only want to add those sprites when the Polygon has been added as child to another node.
Is there any way that the Polygon object itself can detect that it has been added to the scene, or do I need to tell it by making an extra createSprites call after it has been added to the scene?
I guess I can poll the parent attribute, but I'm looking for something event driven.

In Swift, you can define property observers for the properties of the class yours inherits from.
You could observe changes in the parent property of your custom SKNode subclass, like this:
class MyNode : SKNode {
override var parent: SKNode? {
didSet {
// parent node changed; do something
}
}
}
EDIT: Like I mentioned in the comments, in Objective-C (where you can not use property observers) you can instead use Key-Value Observing (KVO), and observe changes in SKNode's parent property:
Actual code:
- (instancetype) init
{
if (self = [super init]){
// REGISTER for KVO
[self addObserver:self
forKeyPath:#"parent"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void) dealloc
{
// UNREGISTER from KVO
[self removeObserver:self forKeyPath:#"parent" context:NULL];
}
// Method that is called when any keyPath you subscribed to is modified
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// (Check keypath in case you are observing several different
// properties. Otherwise, remove if statement:)
if ([keyPath isEqualToString:#"parent"]) {
// parent node changed; do something
}
}

In Objective-C you could do it like this:
[node addObserver:self forKeyPath:#"parent" options:NSKeyValueChangeOldKey context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
//logic here
}
Or in this case simply subclass SKNode and override setParent:

Related

notification for a view x position

in a slide menu I'm developing for my project i would like to add a black view over the content view when it's slide out. To do this i need to create a method that check continuously the view x-position and darken or brighten up the black layer. The position of this view is the same as the content view.
I thought i can use a NSNotificationCenter like this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(incomingNotification:) name:#"notification" object: darkViewController.view.frame.origin.x]];
and a method:
- (void) incomingNotification:(NSNotification *)notification{
// the dark layer alpha will be 0 at origin=0 and 0.8 at bounds.size.width
float alphaToUse = (darkViewController.view.frame.origin.x / self.view.bounds.size.width) * 0.8;
[darkViewController.view setAlpha:alphaToUse];
}
The problem is that i must use an object as parameter.
I'm new to notifications so i'm asking: is it wrong to use them for this kind of things?
Is it better to solve this in another way?
EDIT:
Following Denis advice i'm now trying to use the key-value-observe solution.
My app is structured like this:
MenuViewController-->ContainerViewController-->DarkViewController
In MenuViewController.m :
#interface MenuViewController ()
#property (strong,nonatomic) ContainerViewController *containerViewController;
#property (strong,nonatomic) DarkViewController *darkViewController;
#end
#implementation MenuViewController
#synthesize containerViewController,darkViewController;
# pragma mark - Views
- (void)viewDidLoad{
[super viewDidLoad];
containerViewController = [[ContainerViewController alloc]init];
[self addChildViewController:containerViewController];
[self.view addSubview:containerViewController.view];
[containerViewController didMoveToParentViewController:self];
darkViewController = [[DarkViewController alloc]init];
[containerViewController addChildViewController:darkViewController];
[containerViewController.view addSubview:darkViewController.view];
[darkViewController didMoveToParentViewController:containerViewController];
[UIView animateWithDuration:slideDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[darkViewController.view setAlpha:0.7];
containerViewController.view.frame = CGRectMake(self.view.frame.size.width - slideWidth, 0, self.view.frame.size.width, self.view.frame.size.height);
}
completion:^(BOOL finished) {
if (finished) {
}
}];
[darkViewController addObserver:self forKeyPath:#"darkViewController.view.frame.origin.x" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change: (NSDictionary *)change context:(void *)context
{
NSLog(#"x is changed");
}
When i run this i get this exception:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<DarkViewController 0x10962d280> addObserver:<MenuViewController 0x10922c890> forKeyPath:#"darkViewController.view.frame.origin.x" options:1 context:0x0] was sent to an object that is not KVC-compliant for the "darkViewController" property.'
Ok, it seems that i found a solution following this example Notificationsin IOS
I just added this in the viewDidLoad of my ContainerViewController
[self addObserver:self forKeyPath:#"view.frame" options:0 context:nil];
and implemented the observer method with a for cycle to find my DarkViewController view
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
for (UIViewController * vc in self.childViewControllers) {
if ([vc isKindOfClass:[DarkViewController class]]) {
float alphaToUse = (self.view.frame.origin.x / self.view.bounds.size.width) * 0.8;
[vc.view setAlpha:alphaToUse];
}
}
}
Now i just have to understand where to put the removeObserver method, since my ContainerViewController will be always loaded...
There is another machanism in iOS for such kind of things called Key value coding and Key value observing.
From Notification Center documentation:
As you design your application, do not simply assume that you should send a notification to communicate with interested parties. You should also consider alternatives such as key-value observing, key-value binding, and delegation.
Key-value binding and key-value observing were introduced in OS X version 10.3 and provide a way of loosely coupling data. With key-value observing, you can request to be notified when the properties of another object change. Unlike regular notifications, there is no performance penalty for unobserved changes. There is also no need for the observed object to post a notification because the key-value observing system can do it for you automatically, although you can still choose do it manually.
So if you'll have another notification observers while making slide menu animation it may reduce its handling performance.
And the best solution would be to invoke incomingNotification: method inside the animation block (the method where animation performs).
Apple docs again:
Though key-value coding is efficient, it adds a level of indirection that is slightly slower than direct method invocations. You should use key-value coding only when you can benefit from the flexibility that it provides.
ANSWERING EDITED QUESTION:
This answer describes exactly what you're trying to do. When add the observer on some object's property object's name shouldn't be included in the property key path. So in you case adding an observer looks like this:
[darkViewController addObserver:self forKeyPath:#"view.frame" options:NSKeyValueObservingOptionNew context:nil];
When trying to observe some object property don't forget to ensure the object's class is KVC compliant for that property!
And also don't forget to remove the observers after job is done.

Key-value observing for QLPreviewController.currentPreviewItemIndex

I have an object that need to be notified when a QLPreviewController changes the shown document. QLPreviewController have the property currentPreviewItemIndex that is updated when the document change. I've added my object as observer for currentPreviewItemIndex and it receives the notification when in my code is changed the property, so far so good.
The problem is that the user can change the shown document swiping in the screen and I've found out that in this case the notification isn't generated.
Any solution to receive the notification also in this case? I suppose that the notification is generated when is called the setter of the property currentPreviewItemIndex and probably when the user swipe the property is changed internally in the object QLPreviewController.
Another solution may be to disable the horizontal swipe in QLPreviewController but preserving the vertical swipe (there are the arrows buttons to change the shown document). How do you do that?
Thanks in advance for the help.
Giannandrea
make a category on the QLPreviewController and swizzle the appropriate method and either add the willChange/didChange for KVO ;)
seriously though:
I tried KVO and it didnt work for me either.. 1) id file a bug with apple for that saying you need this
BUT as a workaround
(id )previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
this is called ok and everytime we swipe so I would 'hack' this to FIRE your own correct KVO. something like
static NSInteger oldIndex = -1; //reset when the panel is hidden or shown
int newIndex = qlController.displayedIndex;
if(oldIndex != newIndex) {
oldIndex = newIndex;
[qlController willChangeValueForKey:#"displayedIndex"];
[qlController didChangeValueForKey:#"displayedIndex"];
}
I wrote it inline here so there are bound to be typos and mistakes but I think the general approach could work.
//1. Declare a static context:
static void *changePageContext = &changePageContext;
//2. In viewDidLoad add self as observer for currentPreviewItemIndex property of a strong ref to your QLPreviewController:
[self.previewController addObserver:self forKeyPath:#"currentPreviewItemIndex" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:changePageContext];
//3. Implement the observer method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (context == changePageContext)
{
NSLog(#"newValue:%ld",(long)self.previewController.currentPreviewItemIndex);
}
else
{
// Any unrecognized context must belong to super
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
//4. Remove the observer in viewWillDisappear:
-(void)viewWillDisappear:(BOOL)animated
{
if (![[self.navigationController viewControllers] containsObject: self])
{
[self.previewController removeObserver:self forKeyPath:#"currentPreviewItemIndex"];
}
}

Notification to know nstableview column start resizing

I am working on a MAC application in which I need to get an event that user start resizing column of nstableview. I know there is a notification columnDidResize. But it get called when we ended resizing columns.
Solved the same problem using KVO notifications.
Set your table delegate as an observer for NSTableColumn width:
[column addObserver:self forKeyPath:#"width" options:0 context:nil];
Options argument could be adjusted to get the notification before an actual change takes place.
Then get notified when the width changes:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSInteger resizedColumn = tableView.headerView.resizedColumn;
if (resizedColumn != -1)
{
if (object == column &&
object == [tableView.tableColumns objectAtIndex:resizedColumn])
{
// User is resizing column
}
}
}
Using KVO, as indicated by #pointum works. If you get into troubles related to unregistering the observer, you can do what I actually ended up doing:
Subclass NSTableColumn and override the setter for the Width parameter and send a notification from there.

iOS: addObserver and superview query

I have a uiview named subview1. I add this as subview to a couple of other views depending on certain situations. Now I have the following code
[subView1 addObserver:self forKeyPath:#"superview" options:NSKeyValueObservingOptionNew context:nil];
My problem is the obserValueForKeypath function is never called
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if (self.subView1 == (UIView*)object) {
if ([keyPath isEqualToString:#"superview"]) {
NSLog(#"superview changed %#",change);
}
}
}
Am i doing something wrong here.
Just check that if it is going into first if block , the problem might be there.Also check if you have declared property for variable for which you are setting the observer if it is in different class.

How to target the parent object when using performSelectorOnMainThread?

I have a custom NSOperation object that is instantiated from a UITableViewController subclass (MusicTableVC). The NSOperation object is supposed to populate an NSarray from a URL in the background so the UI doesn't freeze up, but then I need to send that array back to main thread so the MusicTableVC instance can do stuff with it.
I know I need to use performSelectorOnMainThread: to send the array back to the MusicTableVC but to do that I need a pointer to the instance of MusicTableVC.
I was thinking about creating an init method in the NSOperation e.g. initWithParent to pass on a pointer to self and use that but maybe I'm missing something?
#synthesize parent;
- (id)initWithParent:(MusicTableVC*) musicTableViewController
{
if(self = [super init])
{
self.parent = musicTableViewController;
}
return self;
}
-(void) main
}
[parent performSelectorOnMainThread:#selector(arrayFinishedLoading:)
withObject:playlist
waitUntilDone:YES];
}
I think you would do better with blocks and Grand Central Dispatch:
dispatch_async(dispatch_get_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0), ^{
// This is called in background, not blocking the UI
[self populateArrayFromURL];
dispatch_async(dispatch_get_main_queue(), ^{
// This is called on the main thread
[self reportStuffDone];
});
});
that is one way of doing it, but a more common pattern is to have the parent of the NSOperation observe its state, and do something with the results when it is complete. so when you create your operation in the view controller, do something like this:
NSOperation *myOp = [[NSOperation alloc] init];
[myOp addObserver:self forKeyPath:#"isFinished" options:NSKeyValueObservingOptionsNew context:NULL];
then add the KVO callback method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSOperation *myOp = (NSOperation *)object;
[myOp removeObserver:self forKeyPath:keyPath];
dispatch_async(dispatch_get_main_queue(), ^{
// reload the table with the results here
});
}
Your way is fine, I would call it initWithDelegate and define a protocol thou. Just pass the delegate and then the operation is finished just ket it know if it succeded or not.
Recently I've been switching from useless delegates to GCD, so instead I would make something like initWithSuccessBlock and then dispatch it to the main queue. Attention that, if you decide to use this you would have to make sure the block was copied.