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.
Related
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:
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.
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"];
}
}
after an research i discovered the sigleton that may help me but i have some problem to understand how it work.
i need that 2 class comunicate each other, here an example:
i have a tableView wich field are dinamic.
this is my code on viewController.h file:
-(void)ottieniMarche{
responseSimulate = [[NSArray alloc]initWithObjects:#"pollo",#"cane",#"gatto",#"verme",#"gallo",#"topo",#"canguro",#"elefante",#"giraffa" ,nil];
}
Now i have to send this information on my mainTableView.m ad the code that i'm actually using is this:
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
elementMainTableView = [[NSArray alloc]initWithObjects:#"aaa",#"bbb",#"ccc",#"ddd",#"eee",#"fff",#"ggg",#"hhh",#"iii" ,nil];
}
return self;
}
Now i have to change the elementMainTableView value with the responseSimulate value. I mean that the content of my maintableView are the same of my responseSimulate.
If I understand your question correctly, you want to refresh the content of the UITableView based on model changes.
UITableView requires a class that conforms to the UITableViewDataSource protocol to provide its row and section data. Often, that's a UITableViewController, but it does not have to be. The data source for your UITableView could the other class to which you refer. In that case, the key is to ask the reload the data, i.e. [tableView reloadData] when you change the data.
In your case, if your MainTableView (I'm inferring this is actually a UITableViewController subclass...) conforms to the UITableViewDataSource protocol, then you could solve the problem using Key-Value Observing for example: (Note, this example assumes you are using ARC.)
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if( !self ) return nil;
elementMainTableView = [[NSArray alloc] initWithObjects:#"aaa",#"bbb",#"ccc",nil];
[self addObserver:self forKeyPath:#"elementMainTableView" options:NSKeyValueObservingOptionNew context:NULL];
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
if( [keyPath isEqualToString:#"elementMainTableview"] )
{
[[self tableView] reloadData];
}
}
This assumes that you expose elementMainTableView as a property on MainTableView.
Now, in the other class (?ViewController) your method:
-(void)ottieniMarche {
responseStimulate = [[NSArray alloc] initWithObjects:#"pollo",#"cane",#"gato",nil];
_mainTableViewController.elementMainTableView = responseStimulate;
}
For this to work, you will need your ViewController to keep a reference to the MainTableView, e.g. _mainTableViewController above.
I have an NSTextField in my view. Its value is bound to an NSNumber *number in my controller. The controller simply calls through to the model (value) to get the appropriate value.
// In the controller
- (NSNumber *)number {
return [NSNumber numberWithFloat:[model value]];
}
- (void)setNumber:(NSNumber *)aNumber {
[model setValue:[aNumber floatValue]];
}
This is fine, only the controller is not notified of model changes and thus, changing the value in the model does not update the NSTextField.
The only other option I can think of is to have the model notify the controller and the controller manually update the view through an Outlet. But this circumvents the binding.
// In the model, after value change
[[NSNotificationCenter defaultCenter] postNotificationName:#"ValueChanged" object:self];
// In the controller, after being notified
- (void)updateView:(NSNotification *)aNotification {
[myTextField setFloatValue:[model value]];
}
Is there a better, binding-aware way to implement this communication?
I have an NSTextField in my view. Its value is bound to an NSNumber *number in my controller. The controller simply calls through to the model (value) to get the appropriate value.
Why is this property wrapping the value in an NSNumber? KVC will convert primitive values, such as floats, to and from objects for you.
This is fine, only the controller is not notified of model changes and thus, changing the value in the model does not update the NSTextField.
The model changing isn't relevant here, and the controller doesn't need to know about the value of the model's property changing, unless the controller is observing that property—in which case, the controller is getting notified of those changes, because you are using the model's accessors for the property.
The problem is that you have not bound the text field through the model—you're binding it to a fake property on the controller instead.
So, assuming the controller has exposed a property for the model object, simply bind the text field to the controller with the key path model.value.
In your controller, override the keyPathsForValuesAffectingValueForKey: class method. Something like:
+(NSSet*) keyPathsForValuesAffectingValueForKey:(NSString*)key
{
if([key isEqual:#"number"])
return [NSSet setWithObject:#"model.value"];
return [NSSet set];
}
(I'm not at my Mac so this is untested; but the general idea should be sound)
Another option (and what I usually do myself, purely for personal preference) is something like (in the controller):
-(void)setModel:(id)m
{
[model removeObserver:self forKeyPath:#"value"];
[m retain];
[model release];
model = m;
[model addObserver:(self) forKeyPath:#"value" options:NSKeyValueObservingOptionNew context:NULL];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqual:#"value"])
{
[self willChangeValueForKey:#"number"];
[self didChangeValueForKey:#"number"];
}
}
Again, this code is untested, but the general idea should work.