UILabel KVO observing - uilabel

I am going nuts, I looked everywhere on the web but I always found the same code for KVO observing. Yet my observeValueForKeyPath: is never called; this is the simple code I use to observe UILabel taximeterValue:
-(id) initWithCoder:(NSCoder *)aDecoder{
self=[super initWithCoder:aDecoder];
if (self){
[taximeterValue addObserver:self forKeyPath:#"text" options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld) context:NULL];
}
return self;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
if ([keyPath isEqual:#"text"]) {
if (myBookingAlert) myBookingAlert.taximeterValue=self.taximeterValue;
NSLog(#"the text changed");
// put your logic here
}
// be sure to call the super implementation
// if the superclass implements it
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}

A few possible explanations:
taximeterValue is nil.
Nothing is changing taximeterValue.text.
Something is changing taximeterValue.text in a non-KVO-compliant way. Every change to taximeterValue.text must be done either by calling [taximeterValue setText:], or surrounded by appropriate willChangeValueForKey: and didChangeValueForKey: messages.

Related

Detect when SKNode is added as child

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:

Interface Builder: Enabled1,2 property with OR operator

I need to set the property Enabled of a control in interface builder, depending on 2 booleans int he preferences.
However the operator should be OR and not AND. If one of the two is true, than my control should be enabled.
Currently, I can only make it work with an AND operator, (See screenshot).
Thanks
Unfortunately, in IB, you're stuck with and. My suggestion would be to add a new property to an object accessible to your NIB (possibly your owner for the NIB), which is dependent on changes to the other objects in order to enable your control/view.
It looks like you're using the Shared User Defaults Controller, so I would suggest that in the owner you create a new boolean property for your combined user defaults (perhaps downloadingCastOrCrew), and then you'll need to make sure that when either of the defaults change, you change the value of downloadingCastOrCrew:
In your Interface:
#property BOOL downloadingCastOrCrew;
In the implementation as you're setting up your controller or after awakeFromNib:
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
forKeyPath: #"values.kSearchPreferencesDownloadCast"
options:NSKeyValueObservingOptionNew
context:NULL];
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
forKeyPath: #"values.kSearchPreferencesDownloadCrew"
options:NSKeyValueObservingOptionNew
context:NULL];
In the implementation as you're tearing down your controller:
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver: self
forKeyPath: #"values.kSearchPreferencesDownloadCast"];
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver: self
forKeyPath: #"values.kSearchPreferencesDownloadCrew"];
Add an observer if you don't already have one:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.downloadingCastOrCrew = [defaults boolForKey: #"kSearchPreferencesDownloadCast"]
|| [defaults boolForKey: #"kSearchPreferencesDownloadCrew"];
}
By using the accessor method, you'll trigger kvo and you'll be able to use the controller's downloadingCastOrCrew as your boolean to check instead of the NSUserDefaults values directly.
Obviously, if you already have an observeValueForKeyPath, you will likely want to add a context value to the addObserver:forKeyPath:options:context call and check it in the observeValueForKeyPath:ofObject:change:context call.
You can do it in this way :
Create a third property
#property BOOL isFirst;
#property BOOL isSecond;
#property BOOL isTextFieldVisible;//this one is your third
- (IBAction)isSec:(id)sender;
- (IBAction)isFir:(id)sender;
In implementation
- (id)init
{
self = [super init];
if (self) {
self.isFirst=NO;
self.isSecond=NO;
}
return self;
}
- (IBAction)isSec:(id)sender {
self.isSecond=!self.isSecond;
[sender setTitle:[NSString stringWithFormat:#"isSecond: %d",self.isSecond]];
self.isTextFieldVisible=self.isFirst || self.isSecond;
self.isTextFieldVisible=!self.isTextFieldVisible;
NSLog(#"->%d",self.isTextFieldVisible);
}
- (IBAction)isFir:(id)sender {
self.isFirst=!self.isFirst;
[sender setTitle:[NSString stringWithFormat:#"isfirst: %d",self.isFirst]];
self.isTextFieldVisible=self.isFirst || self.isSecond;
self.isTextFieldVisible=!self.isTextFieldVisible;
NSLog(#"->%d",self.isTextFieldVisible);
}
#end
And in the binding just bind the textField to third property,
Check the running application here.
EDIT 1:
Change ValueTransformer NSNegateBoolean in IB. so that my two lines self.isTextFieldVisible=!self.isTextFieldVisible; is not required in both the IBAction.

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.

Objective-C: How to communicate between classes?

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.

autohide only horizontal scroller in NSScrollView

I have an NSTableView, created from IB, that I want to only autohide the horizontal scroller on. The main reason I want to do this is because it seems the NSTableView corverView only get's displayed if there is a vertical scroller.
I can't find any method to do this with the base class. So I tried subclassing NSScrollView and observing the hidden key on the horizontal scroller (code below). This works; however, the view tries to reset the current visible options every time the user resizes the window. This makes my implementation somewhat expensive; and it seems inelegant. Any better ideas about how to do this?
Thanks in advance!
Current implementation:
#interface PVScrollView : NSScrollView {
BOOL autohidesHorizontalScroller;
}
#property(assign) BOOL autohidesHorizontalScroller;
- (void) viewResized:(NSNotification*)notification;
#end
#implementation PVScrollView
#synthesize autohidesHorizontalScroller;
- (void) setAutohidesHorizontalScroller:(BOOL)val
{
autohidesHorizontalScroller = val;
[self setAutohidesScrollers:NO];
[[self horizontalScroller] addObserver:self
forKeyPath:#"hidden"
options:0
context:nil];
}
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (!([self documentVisibleRect].size.width < [[self documentView] frame].size.width) )
{
// remove observer
[[self horizontalScroller] removeObserver:self
forKeyPath:#"hidden"];
[[self horizontalScroller] setHidden:YES];
//[[self horizontalScroller] setNeedsDisplay:YES];
// add it back
[[self horizontalScroller] addObserver:self
forKeyPath:#"hidden"
options:0
context:nil];
}
}
#end
Give this a shot in your NSScrollView subclass:
- (void)setFrameSize:(NSSize)newSize;
{
NSSize minFrameSize = [NSScrollView frameSizeForContentSize:[self contentSize] hasHorizontalScroller:NO hasVerticalScroller:YES borderType:[self borderType]];
BOOL wantScroller = minFrameSize.width > newSize.width;
[self setHasHorizontalScroller:wantScroller];
[super setFrameSize: newSize];
}
You'll need to check "Show Vertical Scroller" and uncheck "Automatically Hide Scrollers" for it to work; I didn't bother making it robust to changes in IB. Also, you'll need to do the same thing when the window is first displayed (in the NSScrollView constructor).
I compared CPU usage with and without this change; it seems to vary at most 1% (19%→20%) in my test application.