Error on creating a KVO on a UIView - objective-c

I am adding a UIView called bookViewContainer to my view controller, and I want to detect when its scale changes using KVO. Here is my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
bookViewContainer = [[UIView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:bookViewContainer];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapRecognized:)];
[bookViewContainer addGestureRecognizer:tap];
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchRecognized:)];
[bookViewContainer addGestureRecognizer:pinch];
[bookViewContainer addObserver:self forKeyPath:#"transform.scale" options:NSKeyValueObservingOptionNew context:NULL];
}
and my observeValueForKeyPath:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"%#", object);
}
However, when I run the project, I immediately get the error:
An instance 0x1d844ca0 of class NSConcreteValue was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
(
Context: 0x0, Property: 0x1d844db0>
)
I've used KVO before (but never on transform.scale), and the view is definitely not being deallocated (I'm using ARC). I've tried using scale instead, but then nothing happens. What am I doing wrong?

Solution: I needed to use transform on its own, rather than a property of it. Job done!

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.

How do you access a uitableview inside a uinavigationcontroller inside a uitabbarcontroller from app delegate?

Im trying to access a refresh control method in a uitableview which is inside a navigationcontroller from a tabbarcontroller which is my root, but im having trouble getting an exact handle.
This is my code so far in the AppDelegate but it doesn't work...
UITableViewController *tableView = (UITableViewController *)[[self.tabbarController viewControllers][0] tableView];
[tableView.refreshControl beginRefreshing];
I have 5 tab bar items which I believe I can access via [0],[1],[2],[3]
And my code in the UITableView (though probably doesnt matter)...
// Add Refresh Control
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:appDelegate action:#selector(forceDownload) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
[refreshControl release];
Any help would be greatly appreciated as i cant find any online that access as deep as this.
If you need communication between objects that are unrelated, i think that the best option is to use NSNotifications. This allows you to use the singleton object[NSNotificationCenter defaultCenter], to pass notifications from one object to another (or many others).
So you can put the AppDelegate object (or other object) to observe a specific notification and then post the notification with the tableviewController when you need to refresh the control.
In app delegate class you can add the observer like:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(forceDownload)
name:#"ForceDownloadNotification"
object:nil];
And, in the tableviewController you can post de notification like:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ForceDownloadNotification"
object:self];
Here, I used the name "ForceDownloadNotification" as the name for the notification. You can use the name that you want, but in order this solution work properly, you must use the same name when you start the observation and when you post the notification.
Here you have a tutorial about this subject.
I like Luis Espinoza's approach but that doesn't answer the question per se.
If you want to call a method inside your UITableViewController nested inside a UINavigationController which is the rootViewController for your App Delegate. First we create a navigationController with the UITableViewController (or a subclass):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
CustomTableViewController *nuTableVC = [[CustomTableViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *nuNavController = [[UINavigationController alloc] initWithRootViewController:nuTableVC];
self.window.rootViewController = nuNavController;
[self.window makeKeyAndVisible];
return YES;
}
Then in your UITableViewController (or subclass) you setup the refreshcontrol just like you asked:
- (void)viewDidLoad {
[super viewDidLoad];
// Add Refresh Control
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:[[UIApplication sharedApplication] delegate]
action:#selector(forceDownload)
forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
}
Finally to access the UItableViewController you must check if the instances are really the classes that you want, here is my implementation for the method you created (forceDownload) in your App Delegate:
- (void)forceDownload {
NSLog(#"force download method in App Delegate");
UINavigationController *someNavController = (UINavigationController*)[_window rootViewController];
UIViewController *vcInNavController = [[someNavController viewControllers] objectAtIndex:0];
if ([vcInNavController isKindOfClass:[CustomTableViewController class]]) {
NSLog(#"it is my custom Table VC");
NSLog(#"here we can stop the refresh control, or whatever we want");
CustomTableViewController *customTableVC = (CustomTableViewController *)vcInNavController;
[customTableVC.refreshControl performSelector:#selector(endRefreshing)
withObject:nil
afterDelay:1.0f];
}
}
I personally prefer using NSNotificationCenter because is simpler, but that doesn't mean we can't access the objects the way you originally planed.
(if you want the example code just ask for it).
Regards.
If your goal is truely to just keep your refresh control synch'd with the networkActivityIndicator then one option is KVO.
In the viewController's viewDidAppear: add something like this
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIApplication *application = [UIApplication sharedApplication];
[application addObserver:self
forKeyPath:#"networkActivityIndicatorVisible"
options:NSKeyValueObservingOptionNew
context:myContext];
self.refreshControl.refreshing = [application isNetworkActivityIndicatorVisible];
}
Then make sure to remove this observer when the viewController doesn't need it - perhaps in viewDidDisappear:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[UIApplication sharedApplication] removeObserver:self
forKeyPath:#"networkActivityIndicatorVisible"
context:myContext];
}
Now for the actual work
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
if (myContext == context) {
self.refreshControl.refreshing = [change[NSKeyValueChangeNewKey] boolValue];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

KVO not working for class property

I'm trying to understand key value observation in iOS but I think I'm not doing something correctly.
As an idea, I tried to add an observer to a view controller's property (a view connected with an IBOutlet). This view (tableIndicator) is animated so I wanted to see if I can get the observer to react when the view's frame changes.
So I did the following, inside the view controller's viewDidLoad:
[tableInidicator addObserver:self forKeyPath:#"frame" options:0 context:nil];
tableIndicator is my view/class property, I'm adding the view controller (self) as the observer, 0 for the default options and frame as the key value being observed.
Then, I'm waiting to see if this function is triggered as the frame changes:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(#"value changed");
}
...but nothing happens.
I'm not necessarily looking for fix to this code since it serves no purpose other than for me to understand it and I would be really grateful if someone could point out to me what I'm doing wrong. Some good examples/tutorials would be awesome too.
The ones I found ( http://iphonedevelopment.blogspot.ro/2009/02/kvo-and-iphone-sdk.html / http://nachbaur.com/blog/back-to-basics-using-kvo ) did not cover such cases. They were only observers applied to a class to watch for one of its properties, not for the property of a (custom)object inside a class, something that I think would be more useful for me.
Thank you in advance
[edit]For those who will miss my comment on the accepted answer:
Changing a view's center will apparently not trigger an observer for the frame property. You have to change the frame itself.
There is something which is not entirely clear about your code. Is tableIndicator a custom class derived from UIView? The method observeValueForKeyPath should be defined inside that class, and it would be then called. But I am not sure this is the best approach.
In general, a sounder approach is to define your controller (not your view) as an observer. In this case you do:
[self addObserver:self forKeyPath:#"view.frame" options:0 context:nil];
from inside the controller at some point; observeValueForKeyPath would also be defined as a method in the controller.
Check that your IBOutlet is connected correctly, probably the tableIndicator ivar points to a nil.
Consider this simple code below, it works. It just creates a window, add a red square on it, then register using KVO the object to be notified for frame change. Finally it instantiates a button: each time you tap on it the frame is reduced by size, and the notification is triggered correctly (you will see the message in the debug console).
So you must check your code.
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
#synthesize v = _v;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
self.v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
_v.backgroundColor=[UIColor redColor];
[self.window addSubview:_v];
UIButton *b = [UIButton buttonWithType:UIButtonTypeRoundedRect];
b.frame=CGRectMake(0, 300, 40, 10);
[b setTitle:#"A" forState:UIControlStateNormal];
[b addTarget:self action:#selector(changeFrame) forControlEvents:UIControlEventTouchUpInside];
[_window addSubview:b];
[_v addObserver:self forKeyPath:#"frame" options:0 context:NULL];
return YES;
}
-(void)changeFrame {
CGRect _f = self.v.frame;
_f = CGRectInset(_f, 20, 20);
_v.frame=_f;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Observing...");
}

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.