I'm trying to observe interface orientation by KVO of UIViewController from other object by this code :
[((UIViewController *)self.delegate) addObserver:self forKeyPath:#"interfaceOrientation" options:NSKeyValueObservingOptionNew context:NULL];
and implementing the function :
-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
if ([keyPath isEqual:#"interfaceOrientation"])
{
// do something
}
}
The method is called only in the first time , although i can see that the property interfaceOrientation of the delegate change while i rotating the phone .
Why?
Please help !
Thanks!!!!
Why are you doing this with KVO? UIViewControllers have built in support for this. Look at
//iOS6 only
- (BOOL)shouldAutomaticallyForwardRotationMethods
{
return YES;
}
and also addChildViewController:
Related
I am trying to implement a KVO example for clipsToBounds property of all subviews in my UIView. I do not quite understand how to change the value in observeValueForKeyPath method. I am using this code:
-(void)ViewDidLoad{
[self.navigationController.view addObserver:self forKeyPath:#"clipsToBounds" options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Triggered...")
}
It is triggered when ever i change the property clipToBounds of a subview that exists in the UIView i have. I need to change the value back to false for every trigger that happens. What should i write inside the observeValueForKeyPath to change the clipsToBounds property? Any help appreciated.
of course adding the Observer must be done before it works.
Guessing your typo in "ViewDidLoad" would just never be called because it should be "viewDidLoad".
Apart from that your KVO pattern could look like..
static void *kvoHelperClipsToBounds = &kvoHelperClipsToBounds;
-(void)viewDidLoad {
[self.navigationController.view addObserver:self forKeyPath:#"clipsToBounds" options:NSKeyValueObservingOptionNew context:&kvoHelperClipsToBounds];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == kvoHelperClipsToBounds) {
NSLog(#"context compared successful...");
//be careful what you cast to.. i dont check isKindOf here.
UINavigationBar* navbar = (UINavigationBar*)object;
if (navbar.subviews.count > 1) {
__kindof UIView *sub = navbar.subviews[1];
if (sub.clipsToBounds) {
dispatch_async(dispatch_get_main_queue(),^{
sub.clipsToBounds = NO;
[self.navigationItem.titleView layoutIfNeeded];
});
}
}
}
// or compare against the keyPath
else if ([keyPath isEqualToString:#"clipsToBounds"]) {
NSLog(#"classic Key compare Triggered...");
}
else
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
[super observeValueForKeyPath...] passes not recognized keyPath to super to let super's class KVO work, otherwise those would be ignored if super's implementation would rely on observing them. Which should also explain how you could observe all subviews if needed. But think about that there could be potentially hundrets of subviews triggering observeValueForKeyPath if a subclass of UIView would implement it and all subviews (or the ones you like) would be inherited also from this special subclass.
When you change clipsToBounds inside the KVO where you observe it, you possibly invoke a loop, specially when you watch both - old and new values. you would change the property, the property triggers kvo, kvo changes the property, the property triggers kvo and on and on.
set [self.navigationController.view setClipsToBounds:YES] to change the property. But if done inside KVO it will trigger KVO again as explained.
Usually you would set clipsToBounds in -initWithFrame: or in -initWithCoder: or via Interface Builder and maybe just observe if it gets changed to adapt some other code.
Sidenote: the context just needs to be unique to distinguish it from other KVO.. it could also be reference to a real objects pointer.
Don't forget added Observers must be removed before deallocation.
I am tracking a progress with a UIProgressView and I set an observer to the property I'm tracking.
I add the observer in the viewWillAppear, like this:
-(void)viewWillAppear:(BOOL)animated
{
[self addObserver:self forKeyPath:#"progress" options:0 context:nil];
}
And when I remove the observer in the viewWillDisappear, like this:
-(void)viewWillDisappear:(BOOL)animated
{
[self removeObserver:self forKeyPath:#"progress"];
}
And in the observeValueForKeyPath method I updated the progress view:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if([keyPath isEqualToString:#"progress"])
{
[self.progressView setProgress:self.progress animated:YES];
}
}
Now when I leave this viewController and I return the observeValueForKeyPath is not being called and I don't get the progressView continuously updated.
The behavior of the progressView according to the code above is shown in this video:
https://youtu.be/PVRT_Jjtdh4
Key-value coding (KVC) is a mechanism for accessing an object’s properties indirectly. In your case "progress" is the property of UIProgressView. But you have registered observer for the UIViewController object which doesn't have 'progress' as property.
Hence replace self with self.progressView
[self addObserver:self.progressView forKeyPath:#"progress" options:0 context:nil];
into
[self addObserver:self.progressView forKeyPath:#"progress" options:0 context:nil];
Also, do the same for detaching observer :
[self removeObserver:self.progressView forKeyPath:#"progress"];
Find updated answer with example. This will help you :
Here you have to register as an observer of the value at a key path relative to the receiver. Also you need to set option.
Use :
-(void)viewWillAppear:(BOOL)animated
{
[[Receiver Instance] addObserver:self forKeyPath:#"progress" options:NSKeyValueObservingOptionNew
context:nil];
}
-(void)viewWillDisappear:(BOOL)animated
{
[[Receiver Instance] removeObserver:self forKeyPath:#"progress"];
}
Rest is as it is.
Here, [Receiver instance] is the instance of class where you value for defined object progress is changing.
Better to remove observer only after class is deallocated. So intsead of placing in viewWillDisappear:animated: you can do the same below :
-(void) dealloc {
[self removeObserver:self forKeyPath:#"progress"]
[super dealloc];
}
I'm stuck with following problem. I have a UIScrollView, _myScrollView, and I want to have another UIScrollView following it's movements. So I'm using key-value observing for the properties "zoomScale" and "contentOffset", but the observeValueForKeyPath:ofObject:change:context: method only report changes in "contentOffset", not in "zoomScale", though the zooming workes fine. (See code snippet below.) What could be the reason for this?
-(void)viewDidLoad {
...
[_myScrollView addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:NULL];
[_myScrollView addObserver:self forKeyPath:#"zoomScale" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"zoomScale"]) {
NSLog(#"zoomScale: %#", change); // Never gets called
}
...
}
The zoomScale property isn't KVO compliant. But UIScrollViewDelegate has a scrollViewDidZoom method that you can use to track changes to zoomScale.
UIKit actually doesn't support KVO.
From the docs:
Note: Although the classes of the UIKit framework generally do not
support KVO you can still implement it in the custom objects of your application, including custom views.
It does work sometimes (as you've seen), but support for it is undocumented and inconsistent. Use the delegate methods instead.
#import "USST_Test.h"
#implementation USST_Test
-(void) observeValueForKeyPath:(NSString *)KeyPath ofObject:(id)object change:(NSDictionary *)
change context:(void *) context{
NSLog(#"yes i have been changed",nil);
NSLog(#"%#",[change objectForKey:#"new"]);
}
-(void)setFirstName:(NSString *)firstName{
[self addObserver:self forKeyPath:#"firstName" options:NSKeyValueObservingOptionNew context:nil];
}
#end
Here is my code and I always get NSNull printed.
I am new to Objective-c and any help is appreciated.
I'm not sure that you understand the purpose of KVO. You're having an object observe itself every time the first name is changed. Typically, another object would add an observer once, using a call like this:
[object addObserver:self forKeyPath:#"firstName" options:NSKeyValueObservingOptionNew context:nil];
This means that self would like to be informed when object changes its firstName key. Calling [object setFirstName:name] will automatically trigger the KVO update, so no extra code is needed in it.
You don't need to implement custom setter for this.
Somewhere when you init the UUST_Test class call
[self addObserver:self forKeyPath:#"firstName" options:NSKeyValueObservingOptionNew context:nil];
And remove
-(void)setFirstName:(NSString *)firstName{
[self addObserver:self forKeyPath:#"firstName" options:NSKeyValueObservingOptionNew context:nil];
}
But be careful to remove the observer when the UUST_Test object will be destroyed. For that you can implement -(void)dealloc method but do NOT call [super dealloc] if you are using ARC which I think you use.
I am working on integrating an ad provider into my app currently. I wish to place a fading message in front of the ad when the ad displays but have been completely unsuccessful.
I made a function which adds a subview to my current view and tries to bring it to the front using
[self.view bringSubviewToFront:mySubview]
The function fires on notification that the ad loaded (the notification is from the adprovider's SDK). However, my subview does not end up in front of the ad. I imagine this is made purposely difficult by the ad provider, but I wish to do it regardless. I am currently discussing with the provider whether or not this can be allowed. But for the time being, I just want to see if it's even possible.
Is there anyway I can force a subview of mine to be the top-most view such that it will not be obstructed by anything?
try this:
self.view.layer.zPosition = 1;
What if the ad provider's view is not added to self.view but to something like [UIApplication sharedApplication].keyWindow?
Try something like:
[[UIApplication sharedApplication].keyWindow addSubview:yourSubview]
or
[[UIApplication sharedApplication].keyWindow bringSubviewToFront:yourSubview]
Swift 2 version of Jere's answer:
UIApplication.sharedApplication().keyWindow!.bringSubviewToFront(YourViewHere)
Swift 3:
UIApplication.shared.keyWindow!.bringSubview(toFront: YourViewHere)
Swift 4:
UIApplication.shared.keyWindow!.bringSubviewToFront(YourViewHere)
Hope it saves someone 10 seconds! ;)
I had a need for this once. I created a custom UIView class - AlwaysOnTopView.
#interface AlwaysOnTopView : UIView
#end
#implementation AlwaysOnTopView
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == self.superview && [keyPath isEqual:#"subviews.#count"]) {
[self.superview bringSubviewToFront:self];
}
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
- (void)willMoveToSuperview:(UIView *)newSuperview {
if (self.superview) {
[self.superview removeObserver:self forKeyPath:#"subviews.#count"];
}
[super willMoveToSuperview:newSuperview];
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
if (self.superview) {
[self.superview addObserver:self forKeyPath:#"subviews.#count" options:0 context:nil];
}
}
#end
Have your view extend this class. Of course this only ensures a subview is above all of its sibling views.
As far as i experienced zposition is a best way.
self.view.layer.zPosition = 1;
Let me make a conclusion. In Swift 5
You can choose to addSubview to keyWindow, if you add the view in the last.
Otherwise, you can bringSubViewToFront.
let view = UIView()
UIApplication.shared.keyWindow?.addSubview(view)
UIApplication.shared.keyWindow?.bringSubviewToFront(view)
You can also set the zPosition. But the drawback is that you can not change the gesture responding order.
view.layer.zPosition = 1
In c#, View.BringSubviewToFront(childView);
YourView.Layer.ZPosition = 1;
both should work.
In Swift 4.2
UIApplication.shared.keyWindow!.bringSubviewToFront(yourView)
Source: https://developer.apple.com/documentation/uikit/uiview/1622541-bringsubviewtofront#declarations