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
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'm trying to figure out how to troubleshoot and/or solve an issue where a UIWebView clearly loads but then disappears.
After tracing in the debugger, the webView is disappearing after viewDidAppear:
The content flashes briefly on screen and is then gone:
To help me trace, I've got some KVO set up to monitor the contentSize:
- (void)viewDidLoad
{
[super viewDidLoad];
[self startObservingChangesInView:self.webView];
}
- (void)dealloc {
[self stopObservingChangesInView:self.webView];
}
- (void)startObservingChangesInView:(UIView *)view {
[view addObserver:self forKeyPath:#"contentSize" options:0 context:&kObservingChangesContext];
}
- (void)stopObservingChangesInView:(UIView *)view {
[view removeObserver:self forKeyPath:#"contentSize" context:&kObservingChangesContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &kObservingChangesContext) {
UIView *view = object;
NSLog(#"View changed for keypath, '%#': '%#'", keyPath, view.description);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Extra context
There's more going on in my UIWebView than shown above, I just wanted to show my current mechanism for monitoring keys.
There are some cases where the load works and stays.
Works
Run app in simulator
Log in
Open Menu
Open Url
Doesn't work
It stops working when following these steps:
Run app in simulator
Log in
Open Menu
Open Url
Logout
Login
Open Menu
Open Url
The problem is that I'm not holding on to any references after logging the user out of the app. The webView is created a new object every time it is pushed with UINavigationController.
So how can I go about solving this? Either there is a known solution or maybe there are there variables I should be tracing with KVO?
Check if your WebView is not being removed from the view hierarchy somewhere by the application. I had similar problem and I've noticed that I unintentionally removed my WebView from the view hierarchy in some event handler.
I faced a similar issue. But the problem with my setup was I was initializing the UI and the view in the Launchscreen.storyboard and not the Main.storyboard. Due to the nature of the Launchscreen.storyboard, it displays my view for some time before vanishing. Putting the view in the Main.storyboard fixed the issue.
What I want to achieve
I have a custom UITableViewCell featuring a UITextField for in-place-editing. A cell's field should be enabled while the UITableView is in edit mode, but NOT while the cell's delete confirmation is showing.
How I'm achieving it
I've subclassed UITableViewCell and overridden its willTransitionToState method thusly:
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
self.nameField.enabled = !(state & UITableViewCellStateShowingDeleteConfirmationMask)
&& (state & UITableViewCellStateEditingMask);
}
The problem
I'm 90% of the way there.
'willTransitionToState' is called after the user presses the cell's '-' button. The delete confirmation is shown and my text field is disabled as desired. But what if the user decides not to delete the cell and hides the delete confirmation by swiping right? In this case, 'willTransitionToState' is not called.
As a result, my text field is stuck in its disabled state even though it should be enabled when the delete confirmation is hidden. You would think that given the fact that a 'UITableViewCellStateShowingDeleteConfirmationMask' flag exists, that 'willTransitionToState' would be called symmetrically, but this doesn't seem to be the case.
UPDATE
It seems that the 'showingDeleteConfirmation' property of UITableViewCell always gives the correct result. So theoretically I could iterate through each cell calling 'showingDeleteConfirmation' and enable or disable each text field accordingly. This is inefficient and kludgy. I'm considering filing a bug report on 'willTransitionToState', but I need more data points. Has anybody else encountered this problem?
4/29/2014
Apple confirms this is a bug. As of today, the bug report is still OPEN.
Declare a context for KVO:
static int KVOContext;
Get the scrollView in the cell with a custom getter:
- (UIScrollView *)scrollViewToObserve
{
UIView *view = self.subviews[0];
return (view != nil && [view isKindOfClass:[UIScrollView class]]) ? (UIScrollView *)view : nil;
}
Add observer to scrollView's contentOffset:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self.scrollViewToObserve addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:&KVOContext];
}
return self;
}
Remove observer in dealloc:
- (void)dealloc
{
[self.scrollViewToObserve removeObserver:self forKeyPath:#"contentOffset" context:&KVOContext];
}
Use KVO to observe changes to contentOffset, but use showingDeleteConfirmation for the enabled state:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == &KVOContext) {
if ([keyPath isEqualToString:#"contentOffset"]) {
self.textField.enabled = !self.showingDeleteConfirmation;
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Warning: View hierarchy can change in future updates
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:
What I have:
When I move a UIView I am detecting its movement by doing the following:
[myView.layer addObserver:self forKeyPath:#"position" options:NSKeyValueObservingOptionNew context:nil];
Being myView the UIView I am moving and self a class I have to detect the different positions the UIView has.
The Issue:
When I put myView inside another UIView and I move the anotherView:
[anotherView addSubview: myView];
The method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
Is not called anymore, although in theory myView is moving as well. I tried to use NSNotification to be fired every time a "movement" occurred, but I find it clumsy. Is there an "elegant" solution for this kind of problem?
For the movement of the UIView I am using this methods:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
I have uploaded a sample project here that does this cleanly:
https://github.com/n9986/ObservingUIViewMovement
I had a similar requirement sometime back. The issue here is that when you add the UIView to another UIView (call it superView), it now resides in the coordinate space of superView. Therefore, the movement of superView in it's parent's coordinate space does not effect it's children.
I will explain the code here a bit.
We have ViewController, MyView and InsideView classes illustrating your typical classes. I want to observe in the viewController whether the InsideView has moved or not. So in the custom class, I added a property positionInWindow and updated it whenever the superView moved.
So in InsideView.m:
// Make sure this property exists in .h file to make the class KVC compliant
#synthesize positionInWindow;
// This method is called when the super view changes.
- (void)didMoveToSuperview
{
// Drop a random log message
NSLog(#"MAI SUPERVIEW HAS CHANGED!!!");
// Start observing superview's frame
[self addObserver:self
forKeyPath:#"superview.frame"
options: NSKeyValueObservingOptionNew
context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// Here we update the positionInWindow because
// we know the superView.frame has changed
CGPoint frameOrigin = self.frame.origin;
[self setPositionInWindow:[[self window] convertPoint:frameOrigin
fromView:self]];
}
And anywhere you want to monitor this view:
// On an instance of InsideView
[insideView addObserver:self
forKeyPath:#"positionInWindow"
options:NSKeyValueObservingOptionNew
context:nil];
The good part about this solution is that InsideView does not need to know who is it's superView. This code will work even if the superView is later changed. There is no modification to the MyView class. And any class can monitor it's property independent of that fact.
As far as I know, UIView's don't move "on their own", which means you probably have some code around there for the movement
Maybe try setting some delegate or notification there instead of using addObserver: